Beyond the discussion in Custom Function Generator Pitfalls, I just thought of another interesting case for my conception of binding…
somevar: 20
body: [
somevar: 10
return do [somevar]
]
test: func [] body
With my model, upon running test
, the function would create a new environment based on that bound to body
. Then it would assign localvar
within that environment (overriding the global assignment). Finally, do
would evaluate the block it’s given; when that block is created, it is bound to the same environment, so it looks up somevar
in that environment, to return 10
.
The flaw in this conception, I’ve just realised, is that notion of ‘creation’. What does it mean for a block to be ‘created’ in Rebol? Because one could just as easily evaluate do fifth body
… and then it’s unclear which context it should be evaluated in. Normally it would be evaluated in the environment of the function, but that environment hasn’t been created yet. Should fifth body
even have a bound environment or not? This is a huge gap in my model.
But not an unsolvable one, I think. Instead of ‘creation-time’, substitute ‘evaluation-time’: when an unbound block is evaluated, the result is a block which is bound to the currently active environment. (When a bound block is evaluated, of course, I see no reason for its binding to change.)
Thus, in the above program, body
is a block which got bound to the global environment when it was evaluated. By contrast, fifth body
never got evaluated, so it has no binding. When test
is called, it creates a new environment, which is set as the current environment; later on, fifth body
is evaluated, at which point it receives a binding to that new environment, so that do [somevar]
picks up the right value of SOMEVAR.
This also implies a very convenient way to create unbound blocks: just quote them! A quoted block evaluates to a block, but that resulting block isn’t itself evaluated, so in this model it never gets a binding. This means, for instance, that I can write my with-function
from the other thread even more simply:
with-return: func [name spec body] [
let passthru: lambda [return] reduce ['lambda spec body]
return spread compose '[(setify name) (bind-to-current 'passthru) :return]
]
(Of course, knowing what I do now about isotopic FRAME!s, it would probably be saner to abandon the explicit binding and do this instead:
with-return: func [name spec body] [
let passthru: lambda [return] reduce ['lambda spec body]
return spread '[(name): (:passthru) :return]
]
So now nothing is explicitly bound, and it would be fine because the body of the inserted FRAME! still carries around its binding to the environment in which it was created. Not that it needs to, because it doesn’t refer to any local variables of with-return
. Either way, the original version remains a good exercise in convoluted binding patterns.)