Soft Quoted Branching: Light, Elegant, Fast

#1

It's uncommon to use expressions that evaluate to branches passed to conditionals. And when you do use one, you probably don't mind putting it in a GROUP! (especially considering that 99% of the time in the far more common cases you were willing to put it in a BLOCK!).

So Ren-C now uses that fact--plus generalized quoting--to allow for a briefer and faster way to evaluate to literals in your conditionals:

>> if true '[block as data]
== [block as data]

Simply pass in a QUOTED! item of any kind, and that item will be what a branch evaluates to. It will be one less level of quoting than what you pass in:

>> if true '<tag>
== <tag>

Previous attempts to get something like this used an /ONLY refinement. But this lets you mix and match in the same operator, as opposed to switching the operator into a "mode":

>> either true '[1 + 2] [1 + 2]
== [1 + 2]

>> either false '[1 + 2] [1 + 2]
== 3

It Solves Some Problems for CASE

Historically, CASE was more lax in accepting types than the corresponding IFs would be:

>> case [1 = 1 <foo>]
== <foo>

It would allow the by-products of arbitrary evaluation to be used:

>> word: <foo>
>> case [1 = 1 word]
== <foo>

Sometimes this resulted in double-evaluation:

>> word: [print "surprise"]
>> case [1 = 1 word]
surprise
== true

The dodgy nature of this "may be a double evaluation, may be not" with no way to tell at source level raised some concerns, which are laid out in the "backpedaling on non-block branches" post.

The combination of soft quoting and generalized quoting lets the same patterns that work for IF work in CASE. It lowers the risks in a legible way:

>> case [1 = 1 '<foo>]
== <foo>

It's Faster and More Efficient

Quoting of up to three levels can be done a cell in place. So '[x] costs less storage (and has better locality with the surrounding cells) than [[x]].

Outside of the reduced storage, it's also lighter on the evaluator.

The consequences

There were very few pieces of code in the Ren-C repo that were affected. One was a help test. It wanted to generate a real-world block to run, and didn't want to call DO for some reason:

for-each w words of lib [
    dump w
    if not set? w [continue]
    if action? get w
        compose [help (w)]   ; errors now...IF thinks the COMPOSE word! is branch
    else [
        help (get w)
    ]
]

It's easy enough to change that to (compose [help (w)])...this kind of usage is very rare.

The one common case of passing code to a conditional originated from Ren-C...the use of lambdas that could take the argument of what drove the conditional:

 trap [1 / 0] then error => [print [error]]

So you have to put it in a GROUP!:

 trap [1 / 0] then (error => [print [error]])

I'm pretty sure this can be rethought to work as before, given the strategy of "right quoting always wins". But the evaluator has some kind of strict horse-blinder rules that guide its design, so not all things are possible. We'll see.

Even if it couldn't get fixed, in the scheme of things it's worth it. And it isn't like people aren't used to putting branches in delimiters for blocks anyway!

1 Like
SWITCH and soft-quoted branches
QUOTED! arrives (formerly known as "lit bit")
Beta/One Quoting in the API decision: rebValue() and rebValueQ()
GET-GROUP!s in PARSE mean "execute and splice as rule"
#2

Here's another great argument for soft quoted branches:

>> 1 + either true [2] [3] + 4
== 7

>> 1 + either false [2] [3] + 4
== 8

If you try that in Rebol2 or Red, you get:

red> 1 + either true [2] [3] + 4
** Script Error: + does not allow block! for its value1 argument

In Ren-C you can even use IF/ELSE interchangeably with EITHER. (This interchangeability now works 99% of the time, so long as you're not trying to get a NULL result out of your truthy branch--nulls will be voidified)

 >> 1 + if true [2] else [3] + 4
 == 7

 >> 1 + if false [2] else [3] + 4
 == 8

This might point to using soft quoting for more things in the system where you think it is infrequent that people will be passing constructed meta-code in.

>> 1 + switch type of #x [issue! [2] tag! [3]] + 4
;-- wouldn't it be nice if this were 7?

It's a bit touchy-feely to make that call. You don't want to have to say compose (reduce [...]) instead of just compose reduce [...], but you're looking to get a block out of it. How many evaluative infix operators want a block out of their left hand side? I think if your construct is explicitly used in meta-coding, you want to bias it to fitting in with other meta-coding without needing to put arguments in groups.

But things like CASE and SWITCH might benefit from soft-quoting their case lists and switch lists, to favor the idea of fitting into more evaluative scenarios for using their product. While not being "a branch" per se, their lists are a kind of a "proxy for branches".