Two months downstream of changing IF to return NULL, I'm now bringing the bootstrap code up to date.
It has to run in a world where a non-branch-taking IF does not vaporize in expressions, because it no longer returns VOID. So the IFs have to be OPT
'ed or ?
'ed or use WHEN
instead.
It's a lot fewer changes than I thought, and usually you can write the code other ways to avoid it. But still, the changes definitely make me sad. I don't like the IF vs. WHEN distinction, and it wouldn't exist in a perfect world.
So I want to look at this again and ask if this is truly the last word--if there is no other option.
It seems now that non-meta assignments of void, e.g. f.dup: void
is going to be the way to make the field unset. That would mean it would do whatever an unspecified refinement would do, e.g. not be taken.
And actually...I'm somewhat torn on whether NULL is what is used in frame building to opt out of refinements (or an <opt>
parameter) or not. This kind of hinges on whether when the frame is built, if the <opt>
does the transformation from void to null during fulfillment, or during type checking. If you literally have to assign parameters to void, then to truly opt-them out (or specialize them out) you would have to use f.^dup: void
... which seems kind of crazy, but, you're asking to drop a refinement off the interface...which you didn't have to do--you could have left it alone and it just wouldn't have been supplied.
Anyway, this is no longer as clear-cut...and in fact, VOID may be necessary.
This is kind of the big deal.
I've been saying you can't unpack unstable antiforms automatically. But... how bad is this? What do you expect if you say:
x: if condition1 [if condition2 [...]] else [1020]
In the case that condition1 is true and condition2 is false, what did you want X to be? Is the error really that bad?
Not being able to unpack a pack doesn't bother the evaluator if you let the result drop on the floor, just if you try to use it.
But if we made branches pack up voids that refused to decay without explicit unpacking, that rules out things like:
append [a b c] case [
condition1 [void] ; note: same as `condition1 []`
condition2 [1020]
]
When VOID works here, you get the nice property that it will still error by default if none of the branches are taken (due to CASE returning NULL on no branch). And then the VOID can opt out.
If that packed the branch so it wouldn't decay, you could still write something like:
append [a b c] opt case [
condition1 [null]
condition2 [1020]
]
That would box the null, but the OPT would also turn a null from no cases into a void, so you'd not get the natural protection from no cases matching.
Looks like Broken Heart May Has To Stay Broken 
This comes down to a contention between "IF being able to return void for no branch" vs. "branches being able to return void".
For THEN and ELSE to react to VOID without reacting to a branch that produces void, you wind up boxing an unstable pack in an unstable pack. Making such an antiform on a casual basis is just not something viable to do.
So as much as I may not like the IF vs. WHEN distinction, it's just one of those things. If you don't like it, there's opt if and ? if, though they're not precise synonyms for WHEN, since they return void should the IF run a branch and evaluate the branch to null.
And you've still got the historical Redbol-style answer, of:
either condition [...] []
It's a pain point, but it just looks like the pain of making ELSE reactive to VOID when it's an unstable antiform creates much more pain. Being able to say ELSE just reacts to "light" NULL--the only falsey value--solidifies the system in other ways.