Why THEN and ELSE are Mutually Exclusive

A NULL produced by a THEN is turned into its isotope...just like running any other branch. So in a chain of THENs: if one THEN runs, all subsequent THEN branches will be assured of running.

 case [
     1 < 2 [print "This runs", null]
     ...
 ] then [
     print "Runs because it receives ~null~ isotope"
     null  ; gets isotopified
 ] then [
     print "Runs because it also receives ~null~ isotope"
 ]

This is the final rule, and I'm going to explain why.

Non-Isotopifying THEN Was a Disaster

At one point I was using Promise chains in JavaScript, and noticed they are designed to work by taking each .then() and feeding it from the previous .then(), where any step can stop the process. In that world, they don't all run just because the first one ran.

So I thought I'd try making THEN not isotopify its branch. If it returned NULL, any subsequent THEN would not run:

; Behavior of a non-isotopifying THEN
;
>> if true [print "runs", null] then [print "runs too", null] then [print "doesn't"]
runs
runs too

It's way too easy to screw up. It bit me several times and I tried to tolerate it, but this real-world code was the last straw:

any [
    not ^rules.1
    find [, | ||] ^rules.1
]
then [
    if not endable? bind param f [
        fail "Too few parameters for combinator"
    ]
    f.(param): null  ; the THEN is thus *incidentally* evaluating to NULL
]
else [
    f.(param): [# rules]: parsify state rules
]

...and that's not even hidden behind a function call--the NULL is right there. Other cases are less obvious:

then [
    ...
    if condition [branch]  ; if the condition is false, the outer ELSE won't run!
]
else [
    ...
]

Too often you write a branch and aren't conscious of what it produces at all. It was a terrible idea.

So THEN must isotopify NULL. How about ELSE?

Once you take away the idea of THENs being conditional on the previous THEN, it's hard to have any sensible reasoning about what the following should do:

case [
    ...  ; (1)
] else [
    ...  ; (2)
] then [
    ...  ; (3)
] else [
    ...  ; (4)
]

I think symmetry is the easiest idea to absorb... that ELSE treats its branch just like any other, and it gets isotopified. That suggests there's no way for the code in (4) above to run.

With this strategy, the only possibilities are:

  • (1) => (2) => (3) - if the case doesn't take a branch it feeds NULL to the first ELSE, which isotopifies its result. This guarantees the THEN will run, which then isotopifies its result...never feeding pure NULL to the last ELSE.

  • (1) => (3) - if the case does take a branch it will isotopify any NULL and thus the first ELSE won't run. It has to pass through the value that it rejected, which gets to the THEN, which runs and isotopifies its result. Again never feeding pure NULL to the last ELSE.

Easy to understand. But it's pointless to put more than one ELSE in a chain.

What About a Branch Type Meaning "Suppress Isotopification"?

For a while I thought the answer might be to create a system-wide concept of requesting not to isotopify branches.

>> if true [null]
== ~null~  ; isotope

>> if true @[null]
; null

If you could do it on any branch that would cover THEN and ELSE too.

However, subverting isotopification is no longer under consideration for branching constructs. It disables building higher level conditionals out of lower level ones.

Making THEN and ELSE exceptions to the rule would wind up competing with the other interesting meanings of @[...] branches that are chosen. But THEN and ELSE want to be compatible with all branch features!

>> if false '[we want] else '[all the features]
== [all the features]

We Shouldn't Be Mimicking JavaScript Anyway :man_facepalming:

Who wants to write a long chain of THENs when you can capture your code in a higher-level CASE-like expression, which has interesting controls for deciding how and when to run through phases?

Or build your own dialect, and inside of that dialect THEN and ELSE can behave differently. But out of the box they isotopify, and are mutually exclusive.

I've cleaned up the 8 posts that were in this thread to beeline to its conclusion: that THEN and ELSE both turn NULL branch products into ~null~ isotopes...and that this is the law of the land for all branches now.

But Requesting Not To Isotopify Was Completely Backwards!

:back:

What we really wanted was a way to ask the THEN or ELSE to decay its input!

It's the consumer of the branch who is weird, not the producer. After all, imagine if that weird "don't decay" branch idea existed:

] then @[
   ... pages ...
   ... of ...
   ... code ...
   null
]
else [
   ... by the time you get here, the @ is far off the screen ...
   ... you've forgotten about it! ...
]

But what if the ELSE said it considers ~null~ isotopes to be the same as NULL. We could have input-decaying THEN and ELSE!

] then [
   ...
   null
]
*else [
    print "This will run!"
]

This puts the idea that you're driving from the nullity of the immediate left result at a more appropriate place.

I've chosen to specialize THEN/DECAY and ELSE/DECAY with the shorthand of *THEN and *ELSE with the star on the left, to accentuate that its doing some pre-processing before the operation. The parallels are DID* and DIDN'T*, which put the star on the right to indicate it's decaying the argument to the right. If anyone has a better idea feel free to suggest it.

Elegant!

2 Likes