Why Doesn't PRINT Return VOID?

Since PRINT doesn't have an interesting return value, we might ask what the harm would be in making it return VOID...

Canonizing VOID Has Misleading Opt-Out Properties

The evolution of void results is such that it's part of the void-in-null-out strategy, as well a way of doing things like opting out of an APPEND without raising an error.

So consider this:

>> if 10 > 20 [<d>]
== ~void~  ; anti

>> if 10 < 20 [<d>]
== <d>

>> append [a b c] if 10 > 20 [<d>]
== [a b c]

>> append [a b c] if 10 < 20 [<d>]
== [a b c <d>]

That feels like a purposeful application of a void state. But if we were to say that PRINT returned void, we'd be condoning things like:

>> append [a b c] print "does this seem right?"
does this seem right?
== [a b c]

Somehow a function with an uninteresting result has been elevated to one that people might start assuming has an interesting, void result. It seems to me that having PRINT return an "ornery" value is a safer and saner strategy:

>> nothing? print "this makes more sense"
this makes more sense
== ~okay~  ; anti

>> append [a b c] print "I like this error"
I like this error
** Error: APPEND does not accept ~ antiform for its VALUE argument

General Argument: Limiting Interface Flexibility

Let's generalize the question to SOME-FUNCTION where the key point is that at the time you write it, you haven't thought of a meaningful result for it.

If at the time of writing a function you know that it doesn't have a meaningful return value, then making it return void--instead of returning a nothing value--ties your hands in changing it.

People will start writing things like:

all [
    ...
    some-function ...  ; user assumes no effect, because of void
    ...
]

But if SOME-FUNCTION had returned a nothing value, then they could have gotten the same effect more obviously with:

all [
    ...
    elide some-function ...
    ...
]

This also gives more freedom to change the interface later, if you think of an interesting value to return. You can progressively add more return types after the fact. But once people assume you always return void, this trap will happen...you're locked in forever in a way that was pretty much completely avoidable.

On a tangential note: I'm not sure why PRINT returns a ~ antiform, and wonder if it wouldn't be better to have it be silent like ELIDE? Would that create havoc if new users did confuse its usage?


2024 UPDATE: Today's PRINT is silent, because the ~ antiform (NOTHING) does not display in the console. Also NOTHING is a branch trigger, so it doesn't cause errors in ANY or ALL...though it does not opt out.


But let's say you wanted actual invisibility--vs. a truthy meaningless result.

We could make print x a synonym for what you'd write today as elide print x. e.g. PRINT would always return an empty pack (~~ antiform) containing no values. That would at least eliminate the possibility of misuse as an argument, as you would get with ELIDE PRINT today:

>> append [a b c] elide print "no dice"
no dice
** Error: 0 Values Available in Empty Pack for APPEND's VALUE argument 

But it would would kill off an interesting feature of being able to respond to PRINT's variant responses in terms of the void-in-null-out protocol.

Today you can use that variance to tell whether the PRINT had output or not:

 >> line: "some text"
 >> (print line) then [print "We had output!"]
 some text
 We had output!

 >> line: null
 >> (print maybe line) then [print "We had output!"]
 == ~null~  ; anti

To do this kind of thing requires having an output value that wouldn't trigger THEN when there is output. So not null, and not void. The nothing antiform works for this, but nihil cannot be used this way.

Admittedly, in practice, this hasn't been used much with PRINT. But PRINT itself uses this feature of WRITE-STDOUT:

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

If You Want, You Can Make Your PRINT Vaporize!

For some, how they want to use the word PRINT may be universal enough that they would rather it be fully invisible. But that would involve a very specific understanding of a very common function...similar to how elide and comment and assert and -- are known to have no result.

All you need to do in your "skin" library is this:

>> print: chain [:lib/print, :elide]
== ~#[frame! {print} [line]]~  ; anti

>> 1 + 2 print "That's it."
That's it.
== 3

But I don't think the average "no meaningful result" function fits in this category, and I'd say I'm fairly skeptical if PRINT belongs in it.