I've hacked up sort of a prototype to look at which managed to do:
>> if true [null] else [<woo>]
>> if false [null] else [<woo>]
(Although the code is disastrous, getting it to work at all required cleaning up some other stuff to be better. So that aspect was good at least.)
It operated on the basic premise I suggested. IF has a named additional output called
>> [value branched]: if true [null]
The system does some implicit wiring to connect that to an input that ELSE can read...in addition to the ordinary value that comes through.
The biggest thing that bothers me about the technique is how easily it is disrupted when the parts aren't fit together in exactly this way.
For instance, right now you'd break it the moment you put the IF into a GROUP!.
>> (if true [null]) else [<woo>]
Though this points to an issue of brittleness with the current concept of multi-returns in general. A SET-BLOCK! wouldn't work with a GROUP! either, since it has to know what function to inject the output requests into, and it doesn't look deeply into groups to find that:
>> [value branched]: (if true [<a>])
(I don't consider this aspect of multi-returns a deal-breaker. The failure mode is a clear error...and you can always use plain refinements or APPLY to get what you're looking for. The SET-BLOCK! is just a convenience.)
There are other combinations that would be weak; such as how
>- is used to invoke enfix from PATH!. That operator doesn't have a mechanism to propagate these requests for additional information:
>> (if true [null]) >- lib/else [<woo>]
== <woo> ; not involved in the multi-return trick
Without another mechanism, there is too much brittleness to accept.
I think we need to avoid any solution where these act differently:
if condition [...] else [...]
if condition [...] comment "Hi" else [...]
(if condition [...]) else [...]
(if condition [...] comment "Hi!") >- else [...]
(if condition [...]) >- else [...]
Voidification May Be Ugly, But At Least It Does That...
Exchanging a single concrete value with known properties works. And we do have the ability to do this now:
>> if true @[null]
You just can't use null producing branches with ELSE, or when they produce NULL they'll run both branches (which may be what you want, in which case, great).
>> if true @[print "A", null] else [print "B"]
A Targeted Solution For JUST THIS PROBLEM May Be Worth It
I was trying to conceive an answer with multiple returns to try and avoid making something that applied only to IF / ELSE. But it got pretty weird pretty fast.
Given the importance of the issue, perhaps some answer that isn't really good for anything besides this conditional situation would be worth it.
Perhaps NULL has an "isotope" that shares its not-able-to-be-put-in-blocks property, but that certain functions treat distinctly. And perhaps it decays, so you can't end up transferring the property into variables besides where the state originates.
That would run into substitutability problems, such as these cases acting differently:
(if condition [...]) else [...] ; isotope NULL passed to ELSE
(x: if condition [...]), (x) else [...] ; isotope "decay" leading to difference
That's a little bit unsettling, but you'd have gotten the same kind of behavior out of the multiple return solution, without the ability to work across GROUP!. It's probably learnable that if you didn't use an ELSE in a direct chain from something conditional, then you get the effect.
I'm worried about any of this being hidden... In the spirit of the named voids, the console should show you something to help discern. One question to answer would be which was the "real null"...I say that should be the thing the most functions return that don't have to worry about this issue.
>> if true [null]
; null (branched)
>> if false [<a>]
Or we could just go all in and say there's just negative and positively charged NULL, and you learn that they get corrupted from one to the other here and there when conditionals are involved. It's a bit like voidification, but not crossing the more obvious datatype barrier to do it:
>> if true [null]
>> if false [<a>]
>> null else [print "Negatively charged nulls run ELSE."]
Negatively charged nulls run ELSE.
>> x: if true [null]
>> x then [print "Positively charged nulls run THEN."]
Positively charged nulls run THEN.
That seems insane, and it really makes me long for the comfort of voidification.
All Answers Must Be Balanced Against Voidification
The concreteness of voidification is still a strong asset, and the @ branches can get those cases where NULL is actually intended covered. So really everything needs to be weighed, here.
I think that naming the voids so you understood why they were voided was a big step in making voidification more accessible and learnable.
Just going to have to keep balancing the equations here as what's possible gets explored. Stay tuned...