In historical Redbol's meaning of the datatype NONE!, it had the bad habit of looking like a WORD!:
rebol2>> 'none
== none
rebol2>> none
== none ; same in R3-Alpha and Red
But it wasn't a word:
rebol2>> type? 'none
== word!
rebol2>> type? none
== none!
It was a distinct type, which also happened to be falsey (while WORD!s are truthy):
rebol2>> if 'none [print "Truthy word!"]
Truthy word!
rebol2>> if none [print "Falsey none!"]
== none
And as we can see, NONE!s served purposes of signaling "soft failures": branches that didn't run, or FINDs that didn't find, or SELECTs that didn't select... etc.
rebol2>> find "abcd" "z"
== none
rebol2>> select [a 10 b 20] 'c
== none
Ren-C Divided NONE!s roles across NULL, VOID, and BLANK!
-
NULL - an "isotopic" state of WORD! that couldn't be put in BLOCK!s. Anywhere that NONE! would be used to signal a soft failure operation--like FIND or SELECT--would use ~null~.
>> null == ~null~ ; isotope >> find "abcd" "z" == ~null~ ; isotope >> select [a 10 b 20] 'c == ~null~ ; isotope >> append [a b c] null ** Error: APPEND doesn't allow ~null~ isotope
-
VOID - the result of things that are effectively "no ops". Some contexts choose to make them vanish, and when functions like APPEND get them as an argument they are treated as no-ops:
>> void ; void results don't show anything in the console >> if null [print "Doesn't print as NULL is falsey"] >> 1 + 2 if null [print "Voids disappear..."] == 3 >> append [a b c] void == [a b c]
(At one time void was also the state of unset variables, but that is now isotopic void...which has reclaimed the name "none")
-
BLANK! was represented by a lone underscore (
_
) and could be put into blocks:>> append [a b c] _ == [a b c _]
It retained the choice to be falsey:
>> if _ [print "Won't print because blanks are falsey"]
Question One: Should BLANK! Just Be A WORD! ?
Ren-C allows you to use underscores internally to words, so it feels a little bad to take away one word.
Outside of historically being hardcoded as falsey, what makes BLANK! fairly "built in" is that in the path mechanics, it fills in the empty slots:
>> to path! [_ a]
== /a
>> as block! 'a//b//c
== [a _ b _ c]
Alternately, we could accomplish a "reified nothing" with a quoted void:
>> to path! [' a]
== /a
>> as block! 'a//b//c
== [a ' b ' c]
But there's other places the blank is used, such as to opt-out of multi-returns.
>> [_ pos]: transcode "abc def"
>> pos
== " def"
Once again, quoted void could be used here...though it looks a bit slight to my eyes, I can also see the point of view that it's better:
>> [' pos]: transcode "abc def"
Broadening the question, we could ask if blank is really the best choice for opting out of things like loop variables in FOR-EACH? Since VOID is used to opt-out, might a lone apostrophe be more coherent?
>> for-each ' [1 2 3] [print "no variable"]
no variable
no variable
no variable
It's more visually slight than for-each _ [1 2 3]
, but not awful. And since voids are used to opt-out in slots it actually dovetails nicely with if the evaluated forms were allowed to opt out via void:
for-each (if false ['x]) [1 2 3] [print "no variable"]
If your expression produces no iterative variables to bind in the body assume that you didn't need them. The alternative of (if false ['x] else [_])
or even (if false 'x else '_)
seems like busywork.
This even would mean you could use ()
as a less slight alternative, if you didn't like the '
syntax:
for-each () [1 2 3] [print "no variable"] ; actually pretty nice!
The premise may hold for multi-returns also:
>> [a ' c]: pack [1 2 3]
== 1
>> c
== 3
>> [a () c]: pack [4 5 6]
== 4
>> c
== 6
I'm not sure it looks worse than [a _ c]: pack [...]
, and would open up:
>> [a _ c]: pack [8 9 10]
== 8
>> _
== 9
>> c
== 10
So can lone apostrophe paper over some of the system-level needs for reified nothingness... at least in enough cases that it's a good tradeoff to give underscore back to wordspace?
Question Two: Does BLANK! Still Need To Be Falsey?
My feeling is that having blank be falsey doesn't have all that much benefit. NULL does a better job of it, and really what it does is mess with its usefulness as a placeholder:
>> append [a b c] all [1 < 2, 3 < 4, _]
== [a b c _] ; would seem nice, but gives error today since ALL is NULL
Thinking of BLANK! as being "null-like" in terms of non-valuedness is generally a hassle. It makes you wonder about whether something like DEFAULT should think of it as being assigned or not:
>> item: _
>> item: default [1 + 2]
== ???
In practice, I prefer the truly non-valued NULL being the only cases that DEFAULT overwrites. This is because NULL is far more useful than BLANK! when it comes to representing something that you think of as "not being assigned"... as you'll get errors when you try to use it places (e.g. in APPEND). Trying to use it to represent nothingness invariably leads to stray appearances in blocks (Shixin wrote a lot of code to try to filter them out in Rebmake, prior to it being switched to NULLs)
This makes more sense, and I think it bolsters the argument that BLANK! is less of a falsey-NULL relative...but more of a placeholder value. I've said "blanks are to blocks what space is to strings". And space is truthy:
>> if second "a b" [print "Space is truthy"]
Space is truthy
>> if second [a _ b] [print "So why shouldn't blank be truthy?"]
???
So Either Way, I Suggest The Removal of BLANK! From Being Falsey. This creates some incompatibility in Redbol (which has been using NONE! as a blank substitute). But it's something that can be worked around.