Why Doesn't PRINT Return Empty Pack ("heavy void")?

Since PRINT doesn't have an interesting return value, we might ask what the harm would be in making it return HEAVY VOID (the empty antiform PACK! containing no values).

Canonizing To Void Has Misleading Opt-Out Properties

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

So consider this (I'm demoing HEAVY VOID's protection against vanishing results in multi-step processes with non-ghostable functions, too...)

>> if 10 > 20 [<d>] else []
== \~,~\  ; antiform (ghost!) "void"

>> print "safety vest", if 10 > 20 [<d>] else []
safety vest
== \~[]~\  ; antiform (pack!) "heavy void"

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

>> append [a b c] (print "safety vest", if 10 > 20 [<d>] else [])
safety vest
== [a b c]

>> append [a b c] (print "safety vest", if 10 > 20 [<d>] else [])
safety vest
== [a b c <d>]

That feels like a purposeful application of a HEAVY VOID state. But if we were to say that PRINT returned HEAVY 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:

>> trash? 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 HEAVY 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 a 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?

Circa 2025, it's now fully decided that the ~ antiform (TRASH) does not display in the console

So that's at least one good reason, because ELIDE returns a GHOST!, and you see that:

>> print "No matter what, you'd see *something*"
No matter what, you'd see *something*
== \~,~\  ; antiform (ghost) "void"

But more generally, do you want this?

>> append [a b c] spread [d e], print "Done."
Done
== [a b c d e]

Whatever arbitrary thing you had before the PRINT would leak through.

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

You can try it out, but I don't think you'll like it. :person_shrugging:

I do occasionally lament the fact that an empty PACK!... a "HEAVY VOID"... that has no values in it and cannot be decayed... isn't the thing that vanishes in the console.

On the surface it feels like a missed opportunity. It's ~[]~ ... it contains nothing... when you're showing the values coming from the pack wouldn't it be great if that just vaporized?

But I've reasoned it through forwards and backwards. And if you get your head around it you start to realize that TRASH! is really the thing that you shouldn't display. The educational value of seeing the difference between knowing you got a HEAVY VOID or a VOID is high. The educational value of seeing a trash is low.

The argument here is airtight. And so this little feel of longing is going to have to live with other little feelings about things that didn't work out. Like "why isn't a single apostrophe a quoted void, and why doesn't that vanish in the console after evaluation":

>> '

>> print "doesn't that *seem* natural?  it was quoted air!"

But we live in a different world, with very good reasons for why it is the way it is:

>> '
== _