Plain GROUP! & Branching: Only Run If Branch Taken?

Something that came up early on was the question of parity in how CASE dealt with GROUP! branches and how IF dealt with GROUP! branches.

Because IF evaluated its arguments, the GROUP! would always run...even if the condition ruled out the BLOCK! that was evaluated to running:

 rebol2>> if true (print "in group" [print "in block"])
 in group
 in block

 rebol2>> if false (print "in group" [print "in block"])
 in group  ; ...still printed...just no "in block"

But with CASE, the branches lived inside a block passed in...making them effectively quoted. Which meant not only could they be skipped, but they were skipped:

rebol2>> case [true (print "in group" [print "in block"])]
in group
in block

rebol2>> case [false (print "in group" [print "in block"])]
== none  ; didn't say "in group" this time

Sensing something was amiss, Ren-C brought these two situations into sync...but biased to the IF behavior. When a CASE saw a GROUP! in a branch slot it would run it, just as an IF would...regardless of whether that branch would run.

ren-c>> case [false (print "in group" [print "in block"])]
in group
; null

It wasn't really a question of which was the more -useful- behavior. Choosing it this way was because it was perceived as the only option. There was no way to stop the GROUP!s from running in IF, so long as the branches were evaluative, this was the only way to get parity...

Suppressing Evaluation Considered for @(gr o up)

When trying to figure out what it was that @(...) did, one potential feature was to remedy this unwanted execution. It would be like (...) but only run if the branch executed.

 >> branchy: func [flag] [either flag '[<a>] '[<b>]]

 >> either true (print "a" branchy true) (print "b" branchy false)
 a
 b
 == <a>

>> either true @(print "a" branchy true) @(print "b" branchy false)
a
== <a>

Useful. But then @(...) became desired for other purposes.

Should Plain GROUP!s Only Run If The Branch Is Taken?

What if the branching constructs quoted the GROUP!s too, and ran them to generate branches only if the condition matched?

 >> branchy: func [flag] [either flag '[<a>] '[<b>]]

 >> either true (print "a" branchy true) (print "b" branchy false)
 a
 == <a>

>> case [false (print "in group" [print "in block"])]
; null

I imagine if you conducted a poll, you'd find a fair number of people who'd say they'd prefer that behavior...and would prefer this to using @(...) to get it.

Other people might say it rocks the boat to have a GROUP! at a callsite that doesn't execute unconditionally. But branch slots are already rocking the boat...they're not evaluative, so they can see quotes:

 >> if true '<foo>
 == <foo>

(You can read all about it with Soft-Quoted Branching...if you've forgotten)

What Would This Inhibit?

For most cases, not running the group unless the branch runs is better. Even if your branch-making code has no side-effects, it's better for performance not to run it unless you have to. Lambdas are a good example...if you wanted to say switch x [...] then (x -> [...]) you could avoid an entire function generation just by having it in a GROUP! like this.

But if you imagine something like a "branch generator" that generates branches in a certain order, and you wanted to position them using that order...you'd need to use a COMPOSE:

 do compose [either (branch-gen) (branch-gen)]

I don't think that's too much of a tax to pay for that intent. It looks fine and conveys exactly what it is...clearly running both branch generations for the COMPOSE step prior to the EITHER.

What I'm more hesitant about is that hard-quoting behavior for branches to not run GROUP!s at the callsites is a fairly early curveball.

But maybe it's just an example of the language being fluid.

Drat! A Mechanical Foil

(I'm not sure if the practice of writing out the reasoning before trying experiments is a good one, because I usually find some issue pretty quickly when I try them...but...)

So the feature of Lambdas for Branches relies on something that is tied to non-hard-quoting semantics.

Rightly or wrongly... the decision was that in an "escapable" quoting slot (e.g. one that would accept a GROUP! and evaluate code)...that escapable slot was also willing to accept something produced by a left-quoting construct on the right hand side.

I'm not too keen on coming up with another parameter convention that is "hard quoted unless a lambda decides it wants to evaluate and put something there". So that sort of kills this idea...so long as it's plain GROUP! that is considered to be what defeats quoting.

But there are still avenues of attack...

One might be that GET-GROUP! is the "sneaky quoting defeater" instead of plain GROUP!. When you think about it, if X and X/Y are quoted by soft quotes but :X and :X/Y aren't... why wouldn't it be :(FOO BAR) instead of (FOO BAR) to undermine a quoting site...with ordinary GROUP!s quoted normally?

So like this:

>> either true (print "a" [<a>]) (print "b" [<b>])
a
== <a>

>> either true :(print "a" [<a>]) :(print "b" [<b>])
a
b
== <a>

To me, that feels more natural...that the colon is asking for subversion of the quote.

Interestingly, you could mix and match...here, seen getting it out of order:

>> either true (print "a" [<a>]) :(print "b" [<b>])
b
a
== <a>

If it were done this way, then it suggests that GROUP! is acceptable as a parameter from direct quoting...but what if you got a GROUP! produced by the subverted quoting?

>> if true :(print "a" '(print "b" [<c>]))
a
b
== <c>

That's just what would naturally happen. Seems all right to me, but it's not clear how you would stop it if you wanted to (as in today's situation, you wouldn't know as the recipient of an escaped quote if what you got was generated by an escape or not.) So if this bothered you, you couldn't use escaping and would have to hard quote it.

This idea feels rather appealing, but it suggests a different semiotics for arguments. e.g. an escapable quoted parameter would be :x or ':x... not '(x)

Anyway, what this really does is raise some questions about how people feel about the quoting of groups and branching.

I am finding myself leaning pretty heavily on the side of saying that it is most convenient if a plain GROUP! in a branch slot does not run its branch generation behavior unless the branch is taken. Which is a bit of a shift for me...as I'd favored "normal" evaluation semantics more often. But it's just seeming that practically speaking it's cleaner to leverage the quoting.

While no one has really commented on this, I think my mind is made up:

It's more intuitive and useful to NOT run the plain GROUP! when the triggering condition is false.

Mechanically, Ren-C has long been able to make a different choice from historical Redbol here, due to changing the parameter conventions to facilitate soft-quoted branching.

...speaking of which, my mind is made up on that, too:

Soft-quoted branching is now foundational. I use it all the time, and like it. And it's an even bigger efficiency advantage in stackless, where you can provide a value directly and not require pushing a stack frame.

So the decision is made.

What if You Want the Branch to Run Anyway?

Right now the answer is to use a GET-GROUP!.

>> branchy: func [flag] [either flag '[<a>] '[<b>]]

>> either true (print "a" branchy true) (print "b" branchy false)
a
== <a>

>> either true :(print "a" branchy true) :(print "b" branchy false)
a
b
== <a>

The reason it works is that right now, GET-GROUP! unconditional processing is done by the soft-quoting parameter convention. So things like EITHER don't have a choice. They receive a plain GROUP! quoted, but a GET-GROUP! will have ran before their code starts.

I'm less sure about this, and it ties into a lot of questions about parameter conventions and things-that-start-with-colon. Some of the more interesting ideas I have for GET-XXX have to do with a meta-like convention that would allow the implementations of things like APPEND to be able to reduce-and-splice:

Beating REPEND: A New Parameter Convention?

So I'm unsure of how what might come out of that could affect these choices. If APPEND expects to receive things like a GET-BLOCK! unadulterated so it can blend reducing with appending, they might expect GET-GROUP!s to be passed as-is also. That might juggle things in these parameter convention ideas.

But I doubt anyone was really clamoring for the ability to run code in groups for branches that aren't taken. If you are deeply affected, feel free to write a long sad essay here. :sob:

1 Like