Rebol2, R3-Alpha, and Red are all quite a bit of a smattering of behaviors for CASE. Ren-C has progressively racheted up the control and semantics, which is particularly important given how free-form case statements are (enforcing pairs of conditions and branches).
But one loose aspect that is still in CASE is the ability to run a block branch evaluatively:
x: true code: [print "This runs because X is true"] case [ false [print "This doesn't run"] x code ]
One has slightly less confidence that code is a branch than if it said
x [do code]. But really, only slightly less...given that X could be a FUNCTION! either way.
So it seems to just tie into the freeform nature of Rebol, all things being equal. If there's no reason to prohibit it, why prohibit it?
But there may be a good reason...
There's a common pattern in case statements where if you reach a certain point in the processing, you want to run some code non-conditionally. With CASE/ALL, you can do this with TRUE in a branch slot, which it will run the branch as it passes by:
case/all [ condition1 [...] condition2 [...] true [ ;-- some code that runs at this point, always ;-- (assuming there wasn't a throw/return/fail...) ] condition3 [...] condition4 [...] ]
It's a clunky way to express it... kind of like having a TRUE condition as your last branch in an ordinary CASE is a clunky way of saying default. But, it works, and people used it.
But what if you have just a normal case? How could you run some processing after some number of cases didn't match, and then resume matching the following conditions?
Technically speaking, you can do it...but in a kind of ugly way that splices the code before the next condition in a GROUP!, so the result of that evaluation is thrown away, and then the result of the group is the condition you actually wanted:
case [ condition1 [...] condition2 [...] ( ;-- some code that runs at this point, if condition1 ;-- and condition2 didn't match condition3 ;-- now process the condition ) [...] condition4 [...] ]
That's extremely inelegant. So much so that people don't do it...they fiddle some variable, exit the case, check the variable and start a new case.
But with the rise of ELIDE, I thought it could save the day:
case [ condition1 [...] condition2 [...] elide ( ;-- some code that runs at this point, if condition1 ;-- and condition2 didn't match ) condition3 [...] condition4 [...] ]
Beautiful! But there's just one problem. Since the blocks are in evaluative slots, they force the hand of ELIDE so it runs when the block after condition2 is processed. This happens whether the branch is taken or not. So you could wind up running the code if condition2 is taken and it never makes it to condition3.
(If you don't understand the mechanics of why the left-to-right evaluation forces ELIDE in this way, read about it here)
If CASE only accepted literal blocks for branches, the problem would go away. And you'd be able to use the ELIDE pattern.
Having pushed the various degrees of freedom around and seeing the tradeoffs, it seems to me that having to say case [true [do code]] instead of case [true code] is a small price to pay.
- CASE structure becomes clearer (though not 100% so, as not all blocks may be assumed to be branches, they could be arguments to a function in the condition)
- Performance goes up...not having to call into the evaluator potentially twice per branch.
- You get the awesome use of ELIDE, and it works in CASE/ALL too (so you don't need to use the TRUE condition "hack")
I think it's a winning proposition. Any objections?
Note that CHOOSE exists now if you are just picking values:
x: 20 result: choose [ x < 10 <thing> x > 10 [literal block] ]
That gets you
result = [literal block]. I'd argue this should not be evaluative for the same reasons, and if you need the thing you're choosing to have some evaluation before choosing use COMPOSE.
text: "thing" x: 20 choose compose [ x < 10 (to tag! text) x > 10 [literal block] ]
We could compromise, with soft quoted slots...
If the slots were soft quoted, then they would evaluate only if a GROUP! or GET-PATH! or GET-WORD!. That would give some flexibility, without needing to worry about participation "in the stream" of the evaluation beyond that single element. This might be a good tradeoff, since as mentioned, the "it's a literal block" doesn't actually guarantee you it's a branch anyway.
(Basically, this just means you'd not need to say COMPOSE in the example above... and you couldn't get literal GROUP!s or GET-WORD!s or GET-PATH!s, you'd have to put them in GROUP!s and quote them.)
This might even suggest an idea for branching in general for IFs, etc...to soft quote the branch slots, which might be safer overall while permitting most of the expressivity people would need.