Limiting PRINT to BLOCK!, TEXT! (perhaps BLANK! to opt-out?)


I've been uncomfortable with PRINT taking ANY-VALUE! for some time.

The reasoning--as I explained--is that BLOCK!s are evaluated. So if you try to use PRINT in a generic way, such as print value to do debug output, you don't know if that is going to do a single or double evaluation. Code that appears to work fine outputting all other values will one day get a BLOCK!, and suddenly you have confusion and/or a serious security problem.

my-routine: func [value [any-value!]] [
    print value

;-- Day 1
x: <a tag>
my-routine x ;-- no problem

;-- Day 2
y: 6.28
my-routine y ;-- yup, fine

;-- Day 304
z: [format hard drive]
my-routine z ;-- uh oh 

(Note: This strongly parallels why there was backpedaling on non-block branches.)

I think that having it accept TEXT! and BLOCK! probably strikes a fair compromise. This way, it's not treated as a fully generic "any-value!" routine, but it still covers the most common non-block case. If people want to print a generic value they'd have to say print [value]...and I think this makes a lot of sense. (Though I can't say we're fully resolved on understanding what that should do, e.g. for instance when value is a BLOCK! itself, at least this is a step forward.)

Bolstering this idea is the strong encouragement of using symbolic routines to dump (--) and probe (??). This is the proper way to get your debug output, and coverage for ANY-VALUE! with no surprise evaluations. Plus, -- gives evaluator invisibility, which is excellent...and PRINT wouldn't give you that (it returns void)

A kind-of-weird idea would be to also allow CHAR!, and have it print the character without a newline. Reason would be to be able to say print newline and have that mean just print literally one newline (not a newline and then a newline). It comes up kind of often, and seems a shame to have to say write-stdout newline or something more drawn out. But also worth pointing out you could get the same effect idiomatically with print [] or print {} and avoid bringing in the extra type and "curious" behavior (that would really only ever be used with newline). Or just print-newline: specialize 'print [line: []]

The rule for accepting blank-in, and having null-out, generally applies only to functions without side-effects. But a "display side-effect" is somewhat different from a data structure side effect. It seems pretty useful here, to have print of blank! be a no-op...and also to be able to throw a THEN or ELSE clause onto that.

Taking a Thrilling Tour Through the DUMP

I decided print newline was clearer. It's the only character PRINT accepts (would be confusing otherwise as to whether it printed a single character or not).

  • print newline prints a single newline (returns void)
  • print {} does the same (returns void)
  • print _ prints nothing (returns null)
  • print [] will print nothing (returns null)

If you look at the implementation of PRINT, it shows how the rules are lining up:

print: func [
    {Textually output spaced line (evaluating elements if a block)}

    return: "NULL if blank input or effectively empty block, otherwise VOID!"
        [<opt> void!]
    line "Line of text or block, blank or [] has NO output, newline allowed"
        [<blank> char! text! block!]
    if char? line [
        if not equal? line newline [
            fail "PRINT only allows CHAR! of newline (see WRITE-STDOUT)"
        return write-stdout line

    (write-stdout try spaced line) then [write-stdout newline]

Notice how SPACED of [] returns NULL, which TRY converts into blank, which WRITE-STDOUT takes to mean it should return NULL. The THEN notices whether write-stdout returns null or not...if it returns non-null (including void) it will follow up with writing a newline.

Because of the way that <blank> works (name still being thought about), this has a nice behavior w.r.t. performance. A function receiving blank that has that parameter annotation won't even try to run.