Regarding Mutual Exclusivity of THEN and ELSE

The rule for voidification of conditionals at the moment is that branching constructs (IF, CASE, SWITCH, etc.) reserve the null result exclusively to mean a branch did not run. Hence if a branch evaluates to null, it is forced to VOID! instead.

>> type of (if true [null])
== void!

This has the pleasing result that you don't get suckered into running an ELSE branch in this case:

>> if true [some-incidentally-null-expression] else [print "Bad if this ran!"]
; void!

Note you don't get an error there, though you would if you tried to assign the result of that IF...ELSE to something. (I'm just rehashing the explanation of VOID! post here, go read that for more information.)

What about THEN and ELSE?

The THEN clause as introduced by Ren-C is a bit of an oddity of programming-language-THENs, because it exists even though you don't generally use it with an IF. You can, but you're just adding an extra clause:

 if condition [
     print "This prints if condition is true"
 ] then [
     print "And this will also print if condition is true"
 ]

It's a bit redundant with IF, though it may help you factor code. But THEN's main use is with more complex branching constructs like CASE or SWITCH, where you want to add code that runs if any of the branches ran.

However, one wonders if THEN itself is in the same category of "voidifying" branches or not. e.g.

if true [
    null
] then [
    null
] then [
    print "Should this print or not?"
]

We can look at the mirror question of ELSE's job regarding its output. Should it nullify it, to keep a nullsy chain all-nullsy...which would line up with voidifying a THEN chain to keep it all value-y?

if false [
    print "Doesn't print"
] else [
    true
] else [
    print "Should this print or not?"
]

But that absolutely should print. Because you want if condition [...] else [...] to act as much like either [...] [...] as possible. That means the ELSE branch should yield its result.

Yet should ELSE voidify its result?

if false [
    print "Doesn't print"
] else [
    null
] else [
    print "Should this print or not?"
]

That's a bit more subtle. The premise I decided to go with was that it should print, e.g. ELSE passes through its result verbatim...without voidification of nulls. Because otherwise, the chain of ELSE...ELSE would never be useful. You'd never run the second ELSE!

So by a similar token, I decided THEN should also not voidify, to avoid chains of THEN...THEN always running together. It seemed strictly more powerful.

The consequence is that you have to be careful with some-branching-construct [...1...] then [...2...] else [...3...]. Because the 2 and 3 branches are not mutually exclusive. You have to read that as:

 (some-branching-construct [...1...] then [...2...]) else [...3...]

e.g.

 (black-box) else [...]

The ELSE is reacting to the overall result of that expression, which includes potentially reacting to the nullness of ...2...

While this defies the conventual notion of exclusivity of THEN and ELSE in most languages, it is strictly more powerful. The lack of a mandatory THEN on IF statements makes this seem like the better choice.

If we thought it worth the tradeoffs to use an arity-1 IF, this choice would absolutely be different. THEN would absolutely have to voidify its output. Because you'd want if (...) then [...] else [...] to have mutual exclusivity of the THEN and ELSE branches. But that's not how Rebol conditionals work.

So right now neither ELSE nor THEN voidify. I've tried it both ways, I can see both sides. But seems to me so long as we're covered for if condition [...] else [...] being mutually exclusive on its branches, I feel the THEN/ELSE chainers are going to be power users who are going to want all the flexibility they can get, which means not locking into a no-nulls-chain-state where you can only put one ELSE and you can't ever turn off the chain once you have one.

1 Like

No one else ever weighed in on this. Which probably means I'm the only one writing THEN/ELSE chains. :-/ But I actually made a mistake with this recently--reminding me that it's another power-vs-safety tradeoff.

Early in Ren-C there was an if* which did not voidify, and you take your chances if you use it. But I didn't like the slippery slope that now every branching construct would need a form that did it "as-is" and did not voidify. Extra forms of CASE*, SWITCH*, etc. I figured power users could leverage THROW and CATCH to get what they needed.

But with soft quoted branching, we get the branch literally...perhaps GET-BLOCK! could mean "run as code, but don't voidify it"?

>> if true [null]
; == #[void]  ; typically no output for void values shown in console

>> if true :[null]
; null

By marking the branch and not the conditional, you wouldn't need extra refinements. So you can say:

>> case [
    1 = 2 [fourth [a b c]]
    1 = 1 :[fourth [d e f]]  ; colon, don't voidify
]
; null

>> case [
    2 = 2 [fourth [a b c]]  ; no colon, voidify
    1 = 2 :[fourth [d e f]]
]
; == #[void]  ; typically not shown in console

Kind of like how soft quoted branching avoided the /ONLY debacle from early IFs:

>> if true [print "Hello"]
Hello

>> if/only true [print "Hello"]  ; available in R3-Alpha
== [print "Hello"]

>> if true '[print "Hello"]  ; modern Ren-C concept
== [print "Hello"]

>> if true '[print "Mix"] else [print "and Match"]
== [print "Mix"]

>> if false '[print "Mix"] else [print "and Match"]
and Match

You can't write :'[print "Mix"], as there is no such thing as a GET-QUOTE!. But you wouldn't need to, because the only QUOTED! that can evaluate to NULL is a quote of nothing. And if you request a literal null, then perhaps voidification is not necessary?

>> if true ' else [print "You said 'literally nothing', why second-guess you?"]
; null

But having exceptions causes problems, and under this proposal you could just say :[null] or :[']

Also we haven't necessarily figured out what :[...] means more generally (REDUCE is a proposal).
But then perhaps for-each :[x y] ... means something distinct from for-each [x y] .... Having branches add yet another interpretation to the mix regarding nullness might be a problem. Maybe :(...) is better. Or maybe for-each :[x y] really does mean "null is acceptable for x and y" and it turns out to be analogous in a quoted position (?)

Anyway, points:

  • Non-mutual-exclusivity of THEN and ELSE is the kind of thing that can bite people
  • You can get into awkward contortions with today's SWITCH or CASE etc. when you wish to evaluate a branch -and- get a null
  • I do not like every branching construct having a refinement form for "do not voidify"; and it may be better to control on a per-branch basis, in a similar way to soft-quoted branching

(I'll mention that outside of @draegtun, no one has really weighed in on soft-quoted branching either...though I have decided it is likely here to stay.)

2 Likes