Why Are Loop Variables Taken Literally?

Historical Rebol takes loop variables by name, literally at the callsite:

 rebol2>> foreach var [a b c] [print ["var is" mold var]]
 var is a
 var is b
 var is c

If you quote the variable (which you might have thought you would have to do, to suppress evaluation), that's not even legal:

 rebol2>> foreach 'var [a b c] [print ["var is" mold var]]
 ** Script Error: foreach expected word argument of type: get-word word block

Ren-C seems to have kept the status quo:

>> a: 10 b: 20 c: 30

>> for-each var [a b c] [print [mold var "is" mold get var]]
a is 10
b is 20
c is 30

Wouldn't it be better if this was done with for-each 'var? It's a tiny bit more typing, but it would make the non-evaluated nature clearer at the callsite.

2 Likes

Significant thought was put into this question, and using the Rebol2 convention wasn't just a matter of "keeping the status quo".

To make a long story short, the needs of the "iteration variable dialect" were such that the decorations had to be used in ways that their evaluator behavior wasn't compatible with, e.g.

for-each var data [...]  ; make new variable named "var", non-meta

for-each $var data [...]  ; reuse existing variable

for-each 'var data [...]  ; make new variable named "var" and don't bind

for-each '$var data [...]  ; reuse existing variable and don't bind

If the iteration variable parameter were not literal, you can't convey the notions because both 'var and $var evaluate to undecorated var. And '$var would evaluate to $var where you could get a signal to reuse the variable, but it wouldn't be bound so you couldn't act on it.

As it turns out, you actually get a bit of an advantage of not creating stray bindings if you use the quoted form. So if you think it reads better at the callsite, you're in luck--especially since doing things like COUNT-UP don't have bindings matter anyway.

But even if the bindings do matter... you can still put your loop variable in a block, if you feel that makes it more clear that it's being dialected.

>> a: 10 b: 20 c: 30

>> for-each [var] [a b c] [print [mold var "is" mold get var]]
a is 10
b is 20
c is 30

Most Rebol programmers would bristle at being required to do that. :man_shrugging:

Admittedly it's pretty strange to read a callsite like for-each '$var and find out that rather than passing unbound material, you've actually passed a bound variable to FOR-EACH. But the need for composable decorations forced my hand... there's only so many parts!

1 Like

Hm, interesting. That's good to know.

>> a: 10 b: 20 c: 30

>> var: ~

>> for-each $var [a b c] [print [mold var "is" mold get var]]
a is 10
b is 20
c is 30

>> var
== 30

That seems useful for performance (if nothing else).


If it's confusing... why is it important to be offering the ability to unbind things via a dialected meaning of quoting? How often does that come up?

1 Like

It's important.

The problem is that since the system takes all bindings seriously, there are many places where if you have something bound and it's not expected, you'll get an error.

For instance: any SET-WORD in a MAKE OBJECT! spec that has a binding creates a contradictory situation. You don't know if the person was intending an assignment to the specific word they composed in, or if a field was supposed to be created for it.

Having a lightweight way to say "don't bind this" really makes a difference in expressivity.

Red changed this to be the default semantic. They didn't want to pay to deep walk and bind the loop body. Ren-C doesn't deep walk the body and mutably bind it (for all the reasons that's a bad idea) so it's not avoiding as much work. You do save on making a new variable, if you already had one on hand. :man_shrugging:

So it's not really that big a deal for performance. It's more stylistic, e.g. if you have code outside the loop body that expects to be able to operate on the item:

x: ()

helper: does [x + 1]

for-each $x [1 2 3] [helper]

Otherwise you'd have to pass the value explicitly:

helper: lambda [arg] [arg + 1]

for-each x [1 2 3] [helper x]

So in that sense you can get some more efficiency and briefer code. It's a feature worth knowing about. But I think creating a new variable is the right default--and Red's choice is a mistake.

1 Like