These are three important "antiforms" you need to know about. (Antiforms can be held in variables, but cannot be put into lists like BLOCK!).
TRASH is the antiform of BLANK!
Trash is used as the contents of an unset variable:
>> x: ~ ; will unset the variable
>> unset? $x ; in the modern world ('x) makes a word with no binding
== ~okay~ ; anti
Its meta-representation is a quasiform shown as a lone tilde (~
), which you can call "quasi blank" if you like (I also call it a "quasar"). So evaluating quasiform blank gives you antiform blank, e.g. TRASH, which has no representation in the console.
>> quasi _
== ~
>> quasar? first [~]
== ~okay~ ; anti
>> ~
>> trash? ~
== ~okay~ ; anti
For reasons that are a bit beyond the scope of this post, TRASH cannot be passed as a "normal" parameter to a function. It represents the state of a parameter being unfulfilled in a FRAME!.
>> make frame! append/
== #[frame! [
series: ~
value: ~
]]
This aligns with some of what Carl wrote about in UNSET! is not first class, and among the implications are that operations like comparison functions do not accept unset states... as in Rebol2:
rebol2>> #[unset!] = 1
** Script Error: Cannot use equal? on unset! value
ren-c>> ~ = 1
** Error: VALUE1 argument of EQUAL? is unspecified
VOID is the unstable antiform of an empty BLOCK! (PACK!)
VOID vanishes in REDUCE and acts as a no-op for things like APPEND. It is the result of eval []
and vaporizes in COMPOSE/etc.
>> void
== ~[]~ ; anti
>> compose [a (if 10 > 20 ['b] else []) c]
== [a c]
>> reduce [1 + 2, if 10 > 20 [<nothing>] else [], 10 + 20]
== [3 30]
>> append [a b c] void
== [a b c]
>> if 10 > 20 ['b] else []
== ~[]~ ; 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 10 > 20 [<foo>] else []
== ~[]~ ; anti
>> any [if 10 > 20 [<foo>] else [], 10 + 20]
== 30
>> all [10 + 20, if 10 > 20 [<foo>] else []]
== 30
>> if (if 10 > 20 [<foo>] else []) [20]
** Error: ~[]~ antiform cannot be used in isolated conditional expressions
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 TRASH 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 third [d e]
== ~[]~ ; anti
>> append [a b c] maybe third [d e]
== [a b c]
To Sum Up...
-
TRASH is the contents of an unset variable
-
It is neither logically true nor false
-
When you reference a variable containing trash using WORD!, it will cause an error
-
It's the typical choice of return value for functions with no meaningful result
-
The console displays nothing when trash is the evaluative product
-
-
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 signal, often meaning "I couldn't find what you were looking for"
-
Because it is a kind of "soft failure", it is the (only) conditionally false value
-
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
-
NULL and TRASH can be held in variables or API handles, but won't be found in lists like BLOCK!.
VOID is an unstable antiform, and can't be saved in variables--only meta-represented.