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!"...it 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.
Anyway...no 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 anyway...it'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 match...so 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 does...in 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.