Should ELSE be FALSE-reactive? (in addition to void and null?)

Historically one might have asked why ELSE doesn't think falseness is something worth reacting to:

>> 1 = 2 else [print "Why not print?"]
Why not print?  ; seems not so bad, right?

The idea was panned, because #[false] was a value and ELSE's main job was to react to the situation of a branching construct that didn't produce a value:

>> math-broken: if 1 = 1 [false] else [true]
== #[false]  ; if this were #[true], math seems broken

But now, the actual falsey ~false~ state is isotopic. And some years of struggle with the parallel problem of what to do with branches that returned "non-values" bore curious fruit: a box that could hold a "non-value" like a null.

>> if false [null]
== ~null~  ; isotope
     ^-- there is no result

>> if true [null]
== ~[~null~]~  ; isotope
     ^-- there is a result and it is null

Isotopic blocks containing one element will decay to that one element in most situations. But ELSE is sensitive to the difference via a ^META parameter. If someone has gone through the effort to box up a null or void vs leave it as a plain isotope, the ELSE assumes it's a meaningful result and should pass it on. And conditional expressions know to do this; they box up nulls and voids if they are produced by executing branches.

The same technique could work for false.

>> if 1 = 1 [false]
== ~[~false~]~  ; isotope

>> if 1 = 1 [false] else [true]
== ~[~false~]~  ; isotope

>> math-broken: if 1 = 1 [false] else [true]
== ~false~  ; isotope

It's barely any additional work for conditionals to do on top of what they're doing already. Although it can result in branches producing false to cost a small bit more than they do today (I wouldn't worry about it, these single element boxes could be optimized if it was a problem)

But I don't know how useful it would actually be. :man_shrugging: Just writing down the observation.

2 Likes

Another idea that has been simmering in my head: What if ELSE was ERROR!-isotope reactive, but only if you use it with a function that takes an argument?

Today that requires you use EXCEPT.

>> if true [1 / 0] except e -> [print mold e]
make error! [
    type: 'Math
    id: 'zero-divide
    message: "attempt to divide by zero"
    near: [1 / 0 **]
    where: [/ if _]
    file: ~null~
    line: 1
]

But what if this happened?

>> if true [1 / 0] else [print "not reached"]
** Math Error: attempt to divide by zero
** Where: / if
** Near: [1 / 0 **]
** Line: 1

>> if true [1 / 0] else e -> [print mold e]
make error! [
    type: 'Math
    id: 'zero-divide
    message: "attempt to divide by zero"
    near: [1 / 0 **]
    where: [/ if _]
    file: ~null~
    line: 1
]

This would conflate errors with null and void. But maybe that's all right.

In the case of UPARSE, pure null or pure void is not returned by combinators at this time. So you'd be able to write combinators using ELSE instead of the uglier EXCEPT. And I guess if you didn't actually feel like giving a full error explaining why you were failing a parse combinator, null isn't a terrible shorthand for "I am lazy"...but we'd have to allow raise null to work as just returning null.

I find it much clearer to use 'except for error handling, and it makes this possible:

>> if true [1 / 0] else [ 2 / 0 ] except e -> [print mold e]
    make error! [
    type: 'Math
    id: 'zero-divide
    message: "attempt to divide by zero"
    near: [1 / 0 **]
    where: [/ if console]
    file: _
    line: 1
]

The remaining question: should except react to the whole expression, or just to the branch it is tacked on?

Currently ELSE passes through an isotopic error.

As evidenced by my questioning of if ELSE with a parameter might act as EXCEPT, I'm not totally firm on this, I guess it really just comes down to what shows it works in practice.

But even though it's a bit messy and emergent, I think isotopic errors are a tremendous step up from the more or less untenable prior error situation.

FAIL vs. RETURN RAISE: The New Age of Definitional Failures!

2 Likes