This idea has been near and dear to my heart: that without any additional work, failed IF returns something that will vaporize in COMPOSE and UNSPACED.
But over time the costs have been adding up. Are these costs too great? Let's look.
It makes IF different from all the other branching constructs
e.g. CASE and SWITCH return NULL when they don't take a branch. IF being different is a design/cognitive load, some might argue.
What if you actually want an IF that returns NULL?
For instance, setting refinements in a frame:
>> bunch-of-dups: 'yes
>> f: make frame! append/
>> f.series: [a b c]
>> f.value: 'd
>> f.dup: if yes? bunch-of-dups [5]
>> eval f
== [a b c d d d d d]
It's not legal to make f.dup
a void, so in this situation we want if null [5]
to be null.
There are workarounds of course:
f.dup: either bunch-of-dups [5] [null]
f.dup: all [bunch-of-dups, 5]
Historical Redbol of course biased this the other way, which is why when you want a COMPOSE slot to go away that's when you have to use either condition [...stuff...] []
Branches Weirder Than They Need To Be, Maybe?
THEN and ELSE MUST be weirder than (deferred) infix IF and IF NOT... because their treatment of "heavy nulls" (e.g. antiform blocks with quasi-nulls in them) differs.
>> if (pack [null]) [print "This does not run"]
>> (pack [null]) then [print "This runs"]
This runs
This is a weird little design edge of necessity, that branching constructs turn "plain null" into "boxed null" in order to signify that a branch ran. We do not want branches that incidentally evaluate to null to lead to else branches running:
>> if 1 = 1 [print "BRANCH", x: null] else [print "ELSE"]
BRANCH
ELSE ; !!! Noooo !!!
But THEN and ELSE are reacting to either unboxed void or unboxed null. Given that NULL is now the only falsey state, might it be better if unboxed null was the only state they reacted to?
There Could Be Shorthands...
OPTIONAL has a shorthand as the ? operator, and this would fit IF into the same strategy as other constructs:
>> compose [<a> (? if null [<b>]) <c>]
== [<a> <c>]
>> unspaced ["a" ? if null ["b"] "c"]
== "ac"
Or maybe the voiding IF has a special name. Like IFF for "IF-and-only-IF", meaning you don't intend to use it with THEN or ELSE, so it might return VOID which they (conceivably?) error on if they're testing for "falsey" ness?
>> compose [<a> (iff null [<b>]) <c>]
== [<a> <c>]
>> unspaced ["a" iff null ["b"] "c"]
== "ac"
Possibly Creative Concepts From if (if ...) [...]
etc.
Turning IF back into something that returns a falsey null permits things like if (if ...) [...]
and other creative concepts, and it's not like there aren't uses in aggregate expressions for an IF that gives falsey results instead of opt out on not branching.
Since both are useful, being able to give an inch here and split it out into two different constructs might be the good answer...making IF like every other conditional construct that returns null on branch not taken?