Having let things settle around in my head for a bit, I think this is the case. And it's a rare occasion of Ren-C coming into alignment with a change to Rebol made by Red. (Though they apparently do not run through common code to test conditionals, since EITHER and IF disagree.)
Also the parallels are inexact, since they have fewer parts...and use UNSET! for things Ren-C does not use NOTHING for.
For instance, NIHIL:
red>> if comment "like this" [print "for example"]
for example
ren-c>> if comment "like this" [print "for example"]
** Script Error: No value in antiform BLOCK! pack: ~[]~ (nihil)
And then Ren-C has VOID:
red>> if (if false [<a>]) [print "for example"]
== none
ren-c>> if (if false [<a>]) [print "for example"]
** Script Error: Invalid use of void
Comparison To JavaScript Undefined
When you think about language mapping, it seems that NOTHING maps to JavaScript's undefined
. But JavaScript makes undefined falsey:
>> if (undefined) {console.log("Them's the breaks.")}
<- undefined
But I think I put together a pretty good rationale for the choice:
Still, it's probably worth doing a little "Nothing Philosophy" just to make sure the type is understood.
To try and characterize nothing, we can look at the places it appears:
Contents of an unset variable
>> asdf
** Script Error: asdf is ~ antiform (see ^(...) and GET/ANY)
Result of a FUNC that reaches its end but does not use RETURN
append-two: func [block value] [
repeat 2 [append block value]
]
>> nothing? append-two (block: [a b c]) [d e]
== ~true~ ; anti
>> block
== [a b c [d e] [d e]]
Lacking a RETURN as a final statement doesn't mean a function always returns nothing, as there may be other control paths in the function that use RETURN to give different results.
Anything That Doesn't Want == Result
In The Console
Ren-C has a big palette of choices to paint another choice for this. It seems awfully tempting to use the ~void~
antiform.
>> compose [1 (if false [2]) 3]
== [1 3]
>> if false [2]
>> if true [2]
== 2
The void just vaporized completely. Doesn't that seem nice and consistent to have it vaporize in the console as well? (By contrast, NOTHING is a BLANK! antiform, and causes errors when you try to COMPOSE it into a block.)
I definitely thought that for a while. But the catch is that VOID is way too nice about opting out of operations. If everything that didn't want to show a console result started returning VOID, you'd have silent opt-outs of places that really should have been erroring on a "meaningless" value.
So it begins to make sense that you show ~void~ antiforms (which offers a concrete education about what the result is)... and you don't show the "meaningless" value. PRINT returns NOTHING precisely because it is not an important return result. And if it's not important, why clutter the display with it?
So far, so good. But the conflation of the "meaningless" value with what is used for UNSET! variables is where it maybe looks not so good.
>> get/any $asdf
>> get/any $asdf
>> print "WHAT THE HECK IS THE VALUE OF ASDF? IS IT VOID? IS IT NIHIL?"
WHAT THE HECK IS THE VALUE OF ASDF? IS IT VOID? IS IT NIHIL?
This might lead us down a line of thinking that we do need something like an ~unset~
antiform that is distinct. (Or we might turn our head around funny and say unsetness keeps ~
NOTHING and anything that wants to be invisible uses an ~invisible~
antiform.)
Here's where I guess I'm just going to have to say "trust me, it's not worth it" (and I've tried it all). You can make peace by saying that a variable holding nothing is meaningless. If you tried to type it in the console, you'd get an error:
>> asdf
** Script Error: asdf is ~ antiform (see ^(...) and GET/ANY)
The fact that you get an error relaxes the concern over it not displaying anything if you do manage to get at its value. If you sneak past the error raising then you'll find the variable is... meaningless. Why display anything?
There are other ways to make peace here, like imagining fancier graphical consoles that animate a little == ~ ; anti
and then it fades away. Or you check a box somewhere and it shows you all the == ~ ; anti
Nothing is NOT Used To Poison Assignments
Worth noticing is that UNSET! can't be assigned to SET-WORD! or SET-PATH! in Rebol2, R3-Alpha, or Red.
>> x: print "Rebol2"
Rebol2
** Script Error: x needs a value
red>> x: print "Red"
Red
*** Script Error: x: needs a value
>> x: print "R3-Alpha"
R3-Alpha
** Script error: x: needs a value
But in Ren-C, assigning NOTHING is allowed... and even considered a quick way to unset a variable.
>> x: print "Ren-C"
Ren-C
>> unset? $x
== ~true~ ; anti
>> y: 10 + 20
== 30
>> y: ~
>> unset? $y
== ~true~ ; anti
What you can't assign is NIHIL, an empty multi-return PACK...because there's nothing to extract:
>> 1 + 1 comment "Hi"
== 2
>> x: comment "Hi"
** Script Error: No value in antiform BLOCK! pack: ~[]~ (nihil)
I think that the historical choice of PRINT returning UNSET! was driven more by the desire to not show anything in the console than it was specifically about trying to avoid assignments to variables. But it was likely considered a bonus, because it would catch unintended assignments.
I don't have any misgivings about allowing SET-WORD! to assign NOTHING. You might lose some error locality. But there's room in the cell for a symbol... if nothing was returned from a function, the name the function was called under could be poked into the nothing in case that helped later.
>> x: print "Save the symbol"
Save the symbol
>> label of get/any $x
== print
>> x
** Script Error: x is nothing (~ antiform), was generated by PRINT
(I am now having nightmares about people exploiting that to sneak data into places.)
Nothing Can't Be Used In Comparisons
If your function ever returns NOTHING, then it likely can't be used in comparisons (consistent with Rebol2 treatment of UNSET!, but not R3-Alpha or Red, which allow comparing UNSET! with EQUAL? etc).
But NOTHING can be tested for truthiness, against a NULL result (or ~false~ antiform).
So Any Big Conclusions Here?
I think a general rule is that unless your function is is proxying arbitrary results (like a DO or similar) then if it originates a return value of NOTHING on its own, it should always return NOTHING (for that configuration of refinements). If it originates any other value, it should probably only be NULL.
This is just based on the idea that you don't want to be throwing callers for a weird loop by having one of many different possible results, where one of them will mysteriously unset their variables or wreck a comparison.
So the only thing you have left as an option is a conditional truthy test: meaningless result that says you succeeded, or a NULL result that means you couldn't do it. PRINT is a good example of that.
(I recently made CALL without /RELAX return NOTHING and I think that's a very good example to help illustrate the premise.)
It's all making sense in my head... for now...