There was a "bug" (or quirk) that the "wave of constness" was not preserved by quoting:
It was giving this behavior:
foo: func [x <local> block] [
block: []
return append block x
]
>> foo 10
** Access Error: CONST or iterative value (see MUTABLE): []
bar: func [x <local> block] [
block: '[]
return append block x
]
>> bar 10
== [10]
>> bar 20
== [10 20]
When you used a quote, you were effectively getting the behavior as if you had fetched the block from a variable. This shielded it from the propagation of <const>
that was coming along the body.
This wound up being useful in cases like the API. I've mentioned before that generalized quoting is your recourse when you are splicing C variable values, and don't get the "fetched-value-is-unevaluated" protection that being able to use a WORD! variable gets you. But it also gets you the ability to bypass the const protection as a variable would allow.
Consider the ordinary code:
>> block: [a b c]
>> repeat 2 [append block 'd] ; first form
== [a b c d d]
>> repeat 2 [append [a b c] 'd] ; second form
** Access Error: CONST or iterative value (see MUTABLE): [a b c]
Then consider the API code:
REBVAL* block = rebValue("[a b c]");
rebElide("repeat 2 [append", block, "'d]"); // errors
While the intent is the first form, the interpreter experiences it as the second form.
So I kept the behavior of quoting being able to bypass this, instead of propagating the const bit:
REBVAL* block = rebValue("[a b c]");
rebElide("repeat 2 [append", rebQ(block), "'d]"); // works
But that means that this also works:
>> repeat 2 [append '[a b c] 'd]
[a b c d d]
It does open some doors to bugs, e.g. if a function does return '(1 + 2) then that group will be mutable... and if the caller appends to it then you get self-modifying code to make return '(1 + 2 + 3) or whatever.
But, it's a tradeoff. You could try to teach people to use the (1 + 2) and save quoting for special cases (today quoted arguments still get the constness, maybe they shouldn't and behave with parity to QUOTED!). Another possibility might be to shift to a "const by default" system where you'd have to make your return values or arguments <mutable>
, else they'd be const.
This has actually crossed my mind before... inspired by the const issue... whether quoting could be used as a kind of "binding shield".
Basically the idea of extending things so that quoting always acts the same as if you fetched the thing that's quoted from a variable. It would preserve what binding it has on it, if any. In a model where things are unbound by default, this would mean they'd frequently be unbound if they originated in source (vs. from a QUOTE or rebQ() operation).
By and large I do find it appealing if "stray" bindings are less common. There are a lot of cases where you want to pass a word somewhere and it's really just meant as a word and not a variable, but you pick up bindings anyway. Same with blocks that come with all kinds of bindings you don't actually want.
But generalizing this breaks a lot of current understandings. This is expected to work, today:
>> foo: lambda [x] [get 'x]
>> foo 10
== 10
New models with completely new understandings might say you need an operator to put a binding on:
>> foo: lambda [x] [get 'x]
>> foo 10
** Error: x is unbound
>> foo: lambda [x] [get bind-to-current 'x]
>> foo 10
== 10
(If BIND-TO-CURRENT is needed often it should have a snappier name. Perhaps this arity-1 operator would even be common enough to take over the name BIND... who knows.)
Or perhaps--again--using the instead of quoting would act different (and again, maybe a bad idea if quoted args don't have parity).
Anyway, just connecting it to a previous issue where quoting stepped in as a surrogate for reference by variable.