Syntax for Subverting Virtual Binding

In the closing weeks of 2020, I've been committing myself to "Virtual Binding's Last Stand". The results are encouraging. While it's a bit premature to declare that "it works!" can bootstrap itself and run the tests without crashing. (This is with applying virtual binding to all XXX-EACH, USE, and MAKE OBJECT! cases.)

Despite the fact that what I have now is kind of the most clunky form of itself, it hasn't degraded performance noticeably. Unfortunately, the main reason it looks so acceptable is because the method it was using from R3-Alpha there before really, really sucked. matter how good virtual binding gets, it will always have more cost than referring to a fixed variable. So if you have a variable you want to reuse, we need a way to say that.

Red's "Idea": Reusing Variables is the Default (and only) option

Here's how Red rolls:

 red>> print-nums: func [] [foreach x [1 2 3] [print ["Value is" x]]]

 red>> x: 10

 red>> print-nums

 red>> x
 == 3

So DocKimbel's answer to the performance and semantic inconvenience of copying-and-rebinding the loop body is "ignore it". You have to define the variable.

 red>> print-nums: func [/local x] [foreach x [1 2 3] [print ["Value is" x]]]

 red>> x: 10

 red>> print-nums

 red>> x
 == 10

This is different from Rebol2 and R3-Alpha, which bear the cost of allocating a new variable, and then copying and rebinding the body to it:

rebol2>> body: [append [] <test>]
rebol2>> foreach x [1 2] body
rebol2>> body
== [append [] <test>]

red>> body: [append [] <test>]
red>> foreach x [1 2] body
red>> body
== [append [<test> <test>] <test>]

(As a point on why this is semantically slippery, notice I didn't say body: [insert body <test>] in the Rebol2 case. The reason is that since the body is copied, you don't have a name for the loop's copy of the body to see the effects on it... so that would change the original body. You have to modify an array stored inside the body to see what's going on. Just another point of complexity in making copies behind-the-scenes...)

Red's Choice is a Bad Default (right?)... but it should be an Option

Even though virtual binding does not copy the body, it won't be without cost. (Its existence isn't really about performance primarily's about semantics, and not corrupting block bindings on a whim.)

So you should have a way of asking to just use an existing variable if you need to.

Early on I used quoted words as a way of telling the FOR-EACH to reuse a variable:

 print-nums: func [<local> x] [
     for-each 'x [1 2 3] [print ["Value is" x]]]

When using a BLOCK! spec, you could mix and some fields were reused and others were part of a generated object:

 print-nums: func [<local> x] [
     for-each ['x y] [1 2 3 4] [print [x y]]]

But I've mentioned that for reasons of one's "formal" senses (or necessary suppressing the behavior of a left-quoting WORD!) that you might want to use quoted words as the argument to FOR-EACH anyway. On top of that, I don't know that we've come to a full consensus that it shouldn't be required...though I'm leaning heavily to saying it shouldn't be.

So what other choices are there? GET-WORD! is already taken to mean "subvert quoting, this word holds the name of the variable(s) to use"... so for-each :x is a shorthand for as a shorthand for for-each :(x)

for-each /x [...] [...]
for-each .x [...] [...]
for-each @x [...] [...]

None of these really scream "use an existing variable" to me. And for-each/reuse wouldn't let you mix and match.

The spec dialect itself could be fancier...maybe importing things #with, and you could use commas to break it up:

for-each [#with x] [...] [...]  ; reuse X
for-each [#with x, y] [...] [...]  ; reuse X, virtual bind Y
for-each [#with x y] [...] [...]  ; both X and Y are reused

Any Other Ideas? Comments?

I'm going to assume everyone agrees that virtual binding should be the default, giving a Rebol2/R3-Alpha-like experience. If you didn't agree with that, and wanted Red's behavior as default, you might suggest things like for-each x: [...] to request a new variable. But that's flat-out confusing, and for-each 'x: [...] isn't much of an improvement.

I'm kind of bummed the distinction of for-each x vs. for-each 'x isn't an option. Something about that seemed nice and clean.

We're still shaping up exactly what the @-family particular if it's going to mean anything w.r.t. datatypes. Maybe that's not its destiny. So best option is probably going to be for-each @x. It leans somewhat on the "as-is" meaning (use the variable as-is).

You won't expect to see this all that often, unless someone is trying to optimize something for performance.

1 Like

I'm not quite clear why for-each x vs for-each 'x isn't an option—it does seem like the cleanest choice.

Please see "Speaking With Tics" (and have an opinion on it!)

My conservative hat would say that the proposals therein aren't worth the cleaner expression up front. It's difficult to be certain due to a dearth of code written with those idioms—they may arguably be more expressive on the whole, but they come at a cost.

That said, I'm not wholly a conservative (that's just the part that is still only productive in Rebol 2), I'm open to seeing how this would resolve.

Helps to be more specific. It's a long post.

You think t: 'type of x is preferable on the whole to t: type of x?

I'd think that you would side on the idea that words should be the currency with as little decoration as possible.

I'd say if type of pushes the cost of clarity elsewhere, then it's more problematic that it's worth.

If for-each 'thing [...] [...] is the natural way to express this behaviour (which it seems so to me—at least compared to the alternatives), then it'd be worth revisiting type of.

I do. I see quoted words in the for-each example as a reasonable tradeoff to express the difference in behaviour moderately consistent with other uses in the language—especially if that is the exceptional case.