Why Doesn't PRINT Return VOID (or Vaporize Via NIHIL) ?

Since PRINT doesn't have an interesting return value, we might ask what the harm would be in making it return a 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.

>> if false [<d>]

>> if true [<d>]
== <d>

>> append [a b c] if false [<d>]
== [a b c]

>> append [a b c] if true [<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:

>> print "this makes more sense"
this makes more sense
== ~  ; isotope

>> append [a b c] print "I like this error"
I like this error
** Error: APPEND does not accept ~ isotope 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 trash 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 trash 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 ~ isotope, 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?

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 (~[]~ isotope) 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!
 == ~  ; isotope

 >> line: null
 >> (print maybe line) then [print "We had output!"]  ; void result

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 none isotope 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]
== ~#[action! {print} [line]]~  ; isotope

>> 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.

@rgchris will be happy to know that after two years of guru meditation, I've embraced the spirit of this proposal

But it's important to point out that two years ago, the proposal of "make PRINT act like ELIDE" had very different implications. In the "pre-isotopic" era, the vanishing property of COMMENT and ELIDE was based on some janky evaluator mechanics that would re-trigger evaluations as long as the function which was invoked was invisible.

The odd re-triggering made things like this possible:

>> append [a b c] (elide print "hi") 1 + 2
== [a b c 3]

Maybe your first impression of that is "cool"! But hopefully you'll be turned against it by reading about its rejection in "Invisibility Reviewed Through Modern Eyes". That explains about how the more precise modern formulation came into being, and how it dovetails with implementing things like multiple return values. (Invisible functions merely return zero multiple return values.)

Various evolutions came to pass, like saying that do [] returns VOID, and that VOID would produce no console output:

>> void

>> do []

>> spaced ["void" "logically" if false ["boo!"] "produces" "no" "output"]
== "void logically produces no output"

Implicit in this evolution was that since since do [] gave void, that a vaporizing result inside that DO (e.g. do [comment "hi"] would not contribute anything, and thus also yield a void.

>> comment "hi"

>> elide print "hi"

If you read this thread and the thread for console treatment of VOID, you will see my struggle with making PRINT act like ELIDE PRINT. But ultimately that is the winning choice. It has pros and cons, but the "pro" of being friendly in first-impression tutorials--while not compromising the rendering of unset variable states--wins out:

Console Treatment of VOID vs. NONE (isotopic void)

While it may seem I take a long time to belabor these basic points, I just want to point out that thinking deeply about the basic points is how you get a nice factoring of parts. If two years seems long, I'll point out that the invention of zero and complex numbers didn't happen overnight, either!