The fallout from the SWITCH fallout feature

To try and be a little bit more clever, Ren-C added the ability of SWITCH to let the last value "fall out". So instead of writing:

result: switch/default "z" [
    "a" [print "won't run" | 10]
    "b" [print "this either" | 20]
][
    30
]

You could just write:

result: switch "z" [
    "a" [print "won't run" | 10]
    "b" [print "this either" | 20]
    30
 ]

Additionally, because Ren-C's switch cases have "soft-quoting" semantics, you could put arbitrary code in a GROUP! and it would fall out.

 var: "a"
 result: switch "z" [
     :var [print "won't run" | 10]
     (first ["b" "c"]) [print "this either" | 20]
     (10 + 20)
 ]

UPDATE Jun 2018: switch now is completely evaluative...not only are GROUP! and GET-WORD! and GET-PATH! evaluated, but so is everything else. You thus need to use LIT-WORD! where you would have used words before. There's a compatibility shim to throw up warnings about the change, so you can't use match expressions like x + y directly yet. But you can if you say switch: :lib/switch and get the real version, which will be enabled by default in the future.

It was an interesting idea that felt a bit like being able to fold defaults into a CASE statement with true [default case code], rather than needing a CASE/DEFAULT.

But with ELSE and THEN and !! and ??, is this feature still interesting?

 result: switch "z" [
     "a" [print "won't run" | 10]
     "b" [print "this either" | 20]
 ] else [
     10 + 20
 ]

Or...

 result: switch "z" [
     "a" [print "won't run" | 10]
     "b" [print "this either" | 20]
 ] !! 30

The fallout feature doesn't have much cost; it's nearly free. And for the performance-minded, it's the fastest way to have a default value.

Does fallout make SWITCH feel like it's fancier and has good dialecting bones, taking advantage of Rebol's unconventional-ness? Or does it make SWITCH seem weird...like you're looking at incomplete code, missing the branch for the last switch case? What do people think looking over it in the above examples?

UPDATE June 2018: Fallout committed for both SWITCH and CASE

1 Like

I should add that I'm seriously considering getting rid of SWITCH/DEFAULT. With void as the generic signal for "this action was a no-op", there's a much richer landscape of ways to deal with defaulting that apply to every operation that obeys the conventions.

More useful IMO would be to add SWITCH/ALL. With the ability to do soft-quoted evaluations, there are actually meaningful use cases for switch/all x [...] also [...] else [...]

This would be a change to the core C native--obviously a SWITCH which supported /DEFAULT could be grafted on top of that.

Update June 2018: SWITCH/DEFAULT eliminated

Removing /default makes sense given the availability of else. I'll add my vote to make this change.
All of these small changes reduce the size of the language hence make it easier to learn.
Good stuff. -John

1 Like

Glad you think so. There's a lot in the balance, though.

One sticking point is that all [() () ()] is void, while all [() false 100] is BLANK!. The former can be used to signal an ELSE, the latter cannot. @rgchris has thought it would seem natural to be able to write all [...] else [...] with the ELSE clause run should any of the expressions be falsey, and I'm sure many people would expect that. But right now it's only triggered by voids, and ALLs are not voided on failure because people like writing if all [...] [x] and not if all? [...] [x] or all [... | x].

So what ELSE means with ANY and ALL winds up like all [...] otherwise-if-all-conditions-opted-out [...]. What we might be able to do is let either BLANK! or void signal an ELSE, and then have taken branches do BAR!-ification instead of BLANK!-ification...

if true [print "hi"] => a BAR!, won't trigger ELSE
if true [blank] => a BAR! won't trigger ELSE
if false [123] => void, will trigger ELSE
any [false false false] => a BLANK!, will trigger ELSE

This preserves the ability of ANY and ALL to safely propagate all-opt-out as void to signal another opt-out, which is important for reasons I have explained before (but can explain again if anyone has forgotten).

I prefer BAR!-ification to truthification or falsification, because since literal BAR!s can't be used in source as arguments they create a bit of a hiccup.

So how about it? BAR!-ification instead of BLANK!-ification of taken conditionals, have THEN and ELSE signaled by "nothingness" as BLANK! or void instead of voidedness?

Update 2018: No, that was a misguided idea. BLANK! is a legitimate value. A much better idea was to rethink NULL, and allow ANY and ALL to return it... be used safely in conditionals...and not stored in variables.

In chat I suggested that maybe logic false should be folded in and be BAR!-ified. But there are probably a lot of SWITCH or CASE usages out there that are generating LOGIC! on purpose.

One of the key reasons that blankification was considered to be safe was due to the historical inability to assign voids via SET-WORD!.

x: case [true [print "this would not work in R3-Alpha"]]

So thus far, if that has come up BLANK! under the new rules to facilitate THEN and ELSE, it seems all right. But there's probably a lot of this kind of stuff:

flag: switch text [
    "on" "yes" "true" [true]
    "off" "no" "false" [false]
]

BAR!-ification of LOGIC! false would break that, requiring SWITCH*. That looks like too much of a sacrifice.

But would BAR!-ification of BLANK! be that bad? How often is BLANK! intentionally the return result of conditional branches? Is the desirability to use ANY and ALL with THEN and ELSE high enough to push intentional-blank-return into the "expert" domain of SWITCH* or SWITCH/ONLY ("if I say void or blank, that's what I meant the result to be...don't BAR!-ify them for the sake of THEN and ELSE")

Offhand I'd say it is probably sufficiently desirable.

Update 2018: No, it wasn't.

Hrm. Well, experimenting with it, I feel like it crosses a line.

Here's a sample of a real-world casualty of a plain CASE wishing to intentionally return a BLANK!, from BrianH's LOAD code. (I'm linking to the original R3-Alpha repository to best reflect the original intent.)

Basically, even if you supply a /TYPE to this routine, it was legal to use a BLANK! to indicate that there is no type.

The error message you get is not too informative: read-decode does not allow bar! for its type argument. At least it is an error message, but this almost calls for a specialized VOID-OR-BLANK-FROM-BRANCH! datatype to clue you in, rather than leave you completely in the dark. :-/

The problem here is that BLANK! is a value, and it's a value that we are encouraging people to use instead of inventing their own way of saying "nothing" on each type. In practice, not letting branches evaluate to it feels significantly more disruptive than not letting branches evaluate to void (especially for the reasons mentioned about how a void/unset! couldn't even be used as an assignment source previously, and now it can't be put in blocks either).

Still good things to try out, even if it's just to be able to explain what not to do when people ask "why didn't you do X".

Update 2018: ^-- that part is completely true.

This thread went off into tangents about defaulting which were (fortunately) eventually resolved, in a very nice way.

That's great--but it never resolved the original question:

I'm going to come out in favor of the switch fallout feature. Reasons:

  1. Performance-wise, it is the fastest defaulting mechanism...faster than ELSE or SWITCH/DEFAULT.

  2. If people don't like it, they don't have to use it.

  3. Due to the nature of ELSE, it completes the left hand side of an expression. Sometimes when an expression can be completed you don't want it to...and this means you need <-, so for instance you have to write append x <- switch [...] else [code]. But with switch fallout, you can do it as append x switch [... (code)].

Point 3 is a reason to not blindly convert EITHERs into IF...ELSE. Or CASEs ending in true [...] to CASE...ELSE. Sometimes it's better to duck an arrow.

I've also noticed that CASE ending in true [...] can be more compact than a CASE...ELSE. That applies to the fallout switch too, it's just shorter.

(Note: As some have pointed out, writing /else [...] as your last "truthy" case is actually faster, because there's no variable lookup involved (true => #[true]), /ELSE is just "truthy" by nature. So we should probably prefer that as an idiom.)

These arguments seem to favor the feature, so I'm going to consider it locked for Beta/One. But /DEFAULT is going to get dropped...and only be available in legacy.

Now a new question...

Should CASE also support fallout?

There've been some increases to the strictness of CASE. It now pretty much requires blocks for the clauses, and given that it does that, might we allow:

>> case [
       1 > 2 [...]
       3 > 4 [...]
       10 + 20
   ]
== 30

It would be consistent with SWITCH, and the risks of such a construct are much lower than they used to be. Note that since there's strict alternation, you couldn't say 10 + 20 30, it would measure that exactly one expression ran and then it either ends or there's a source-level block.

A lot of the same arguments for why it's good for SWITCH apply here, too. (And of course, useful for Code Golf...) Certainly the "you don't have to use it if you don't want to" seems a pretty strong point.

Update: Fallout from CASE committed