2022 UPDATE: Modern thinking in Ren-C is that void states only vanish in "interstitial" slots. They can be recorded in variables, just not be put in blocks:
>> x: void ; void
The question of how permissive to be is still open, and at time of writing it does this:
>> x ; void >> 1 + 2 x == 3
This impacted some elements of the discussion, so I edited the post accordingly to bring it up to date.
Since PRINT doesn't have an interesting return value, we might ask what the harm would be in making it invisible, vs give back something that trips up things like ANY and ALL.
But 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.
Limiting Interface Flexibility
If at the time of writing a function you know that it doesn't have a meaningful return value, then making it 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 invisible
...
]
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.
In PRINT's Case, a Differentiated NULL Output Has Value
Having a return value from PRINT that is either an ornery value or nothing lets you offer the neat option of returning NULL if the result of the print was nothing.
>> 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
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.
I might be convinced that how people want to use PRINT is universally enough that they would rather it be 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.
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.
I Think "Void" Functions Should Be Used Sparingly
I think the feature should be discoverable, because how it's done is unique...and we are giving it a more normal name ("void function")
And it's all right that these forms of RETURN generate void functions:
f1: func [x] [return void]
f2: func [x] [return comment x]
f3: func [x] [return ()]
f4: func [x] [return (void)]
f5: func [x] [return (comment x)]
But this arity-0 return should not be a void function:
f6: func [x] [return]
Instead it should be either an error, or act like return none (e.g. return ~[]~
, an isotopic block representing an empty parameter pack). While the latter sounds neat, it creates problems if people depend on it and start thinking they can throw a plain return
in on its own line and not bother commenting things out after it... but the return picks it up:
Line Continuation and Arity Bugs: Thoughts?
So it's something that should not be allowed, or that has mitigation for this "return put on its own line in the middle of code" vulnerability.