Ren-C has some very carefully designed distinctions of "nothingness"--with subtleties that go far beyond something like JavaScript's null
and undefined
.
While it might seem overwhelming at first, they are purposefully chosen, and a pleasure to work with in practice. Here's an attempt to run through what they are and how they are used.
VOID is the antiform of the WORD! "void".
It vanishes in REDUCE and acts as a no-op for things like APPEND. It is also the result of an IF that doesn't take its branch, and vaporizes in COMPOSE/etc.
>> void
== ~void~ ; anti
>> compose [a (if 10 > 20 ['b]) c]
== [a c]
>> reduce [1 + 2, if 10 > 20 [<nothing>], 10 + 20]
== [3 30]
>> append [a b c] void
== [a b c]
>> if false ['b]
== ~void~ ; anti
VOID will opt out of ANY and ALL. But because of this, an isolated conditional like IF can't make a logically consistent decision about it being a "branch trigger" or "branch inhibitor", it gives back an error:
>> if false [10]
== ~void~ ; anti
>> any [if false [10] 10 + 20]
== 30
>> all [10 + 20 if false [1020]]
== 30
>> if (if false [10]) [20]
** Error: ~void~ antiform cannot be used in isolated conditional expressions
NOTHING is the antiform of BLANK!.
I chose the name NOTHING for the contents of an unset variable:
>> x: ~ ; will unset the variable
>> unset? $x ; in the modern world ('x) makes a word with no binding
== ~true~ ; anti
Its meta-representation is a quasiform shown as a lone tilde (~
), named TRASH. So evaluating TRASH gives you NOTHING, which has no representation in the console.
>> quasi _
== ~
>> trash? first [~]
== ~true~ ; anti
>> ~
>> nothing? ~
== ~true~ ; anti
For reasons that I have outlined elsewhere, NOTHING is a branch trigger:
>> print pick ["hello" "goodbye"] 1 ; result is nothing, no console display
hello
>> print maybe pick ["hello" "goodbye"] 3
== ~null~ ; anti
>> if (print pick ["hello" "goodbye"] 1) [<printed>]
hello
== <printed>
>> if not (print maybe pick ["hello" "goodbye"] 3) [<silent>]
== <silent>
Among the implications here, you can safely use PRINT statements in ANY and ALL (though they're not ignored, and if it is the last statement the overall result will of course be nothing... so use ELIDE PRINT if you want it to be no vote).
>> any [print "Hello", null, 1 + 2, 10 + 20]
Hello
== 3
>> all [1 + 2, 10 + 20, print "Goodbye"]
Goodbye ; nothing result means no subsequent == in console
>> all [1 + 2, 10 + 20, elide print "Goodbye"]
Goodbye
== 30
NULL is the antiform of the WORD! "null".
In the API this is represented as the 0 pointer and does not require having its handle released, so it is like C's NULL. It is used as an "ornery nothing"...but unlike NOTHING it doesn't indicate an unset variable, so it can be fetched by normal WORD! access. The system accomplishes elegant error locality using the VOID-in-NULL-out protocol in many places, which hinges on the MAYBE function that converts NULL to void.
>> case [1 > 2 [<a>] 10 > 20 [<b>]]]
== ~null~ ; anti
>> reduce [1 + 2 case [1 > 2 [<a>] 10 > 20 [<b>]]] 10 + 20]
** Error: can't put ~null~ antiforms in blocks
>> reduce [1 + 2 maybe case [1 > 2 [<a>] 10 > 20 [<b>]]] 10 + 20]
== [3 30]
>> third [d e]
** Script Error: cannot pick 3
>> try third [d e]
== ~null~ ; anti
>> append [a b c] try third [d e]
** Error: Cannot put ~null~ antiforms in blocks
>> compose [all your base (try third [d e]) are belong to us]
** Error: Cannot COMPOSE ~null~ antiforms into slots
>> maybe try third [d e]
== ~void~ ; anti
>> append [a b c] maybe try third [d e]
== [a b c]
NIHIL is an empty antiform block
This is a parameter pack with no values in it. This unstable isotope can't be stored in variables or API handles, and can only be handled in its meta form. (Explanation is beyond the scope of this thread...so read the post on modern invisibility to understand why the shade of distinction from VOID is justified.)
If you want an EVAL to vanish (in the spirit of do []) then it does so with a switch passed to it:
>> reduce [1 + 2 eval/vanish [] 10 + 20]
== [3 30]
But without the switch, it will be NOTHING and cause errors in these cases. But if the contents of the block evaluate to void, that overrides the NOTHING result and it will overall evaluate to void, and vanish in these contexts.
>> reduce [1 + 2 eval [if false [<unused>]] 10 + 20]
== [3 30]
To Sum Up...
-
VOID is intentional emptiness--tolerated many places as meaning "I'd like to opt out please"
- Since it opts out of aggregate conditional tests, it can't logically be acted on in an isolated conditional expression like IF
-
NULL is a nothingness signal, often meaning "I couldn't find what you were looking for"
-
Because it is a kind of "soft failure", it is treated as conditionally false
-
Also because it is a soft failure, most non-conditional slots reject it as an argument
-
MAYBE can be used tactically to convert NULL results to VOID
-
But then antiform BLANK! comes in to be NOTHING...the "bad check". A variable holding it is considered to not be set, and it trips up access via WORD!.
All three of these states can be held in variables or API handles. And then pure invisibility is built upon a weirder mechanic of NIHIL, which can only be handled by ^META-aware code. You don't need to know how it works to use it (the implementations of COMMENT and ELIDE are trivial in both the main language and UPARSE combinators). But the mechanics are there required to implement them.