Block Creation Vs. Evaluation

One thing I’ll note, on reflection: you can fix this behaviour simply by getting FOR-EACH to do a traversal of the block it’s supplied, and add its new environment to any nested blocks as well. That’s not a very desirable approach, but it does mean that it’s still theoretically possible to get that behaviour back.


I think this is one place where we can be quite precise: it works best when the two relevant environments share a common ancestor. The further away the common ancestor is, the more difficult it will be to reason about the behaviour of unbound code.

Yep, this is a much more difficult case to deal with for my model. The fundamental problem is that it wants the environment of a block to be fully described by binding onto that block value itself. That’s easy if the block is evaluated in its current environment without further modification. That’s also easy if the block is passed directly to the function which wants to modify its environment. But, if the block is merely included somewhere else, it doesn’t know that its calling environment has been modified.

I think there’s a way around it, though. In particular, we could use the same idea that I suggested for custom function generators, namely passing new bindings through functions:

loop-five: lambda [:var body] [
    for-each :var [1 2 3 4 5] compose [
       print "Iterating loop"
       (lambda compose [(:var) continue] body) (:var) :continue
    ]
]

This obviates the need for UNUSE… or, perhaps, reframes it as a tool to generate these pass-throughs in a convenient way:

>> code: unuse [x] [print [x + y]]
== (~#[frame! [x]]~ x)

(Where that outer GROUP! is unbound, of course.)

So with that UNUSE it would look like:

loop-five: lambda [:var body] [
    for-each :var [1 2 3 4 5] compose [
       print "Iterating loop"
       (unuse compose [(:var) continue] body)
    ]
]

I don’t actually mind this style — the way I see it, it’s good to precisely express intent like this. It means the default is for blocks to be executed in the same environment they were defined in, and overriding that requires special work (though not much!) and is done in a limited way. Certainly, it’s not obviously worse than the current situation, which is ‘blocks can have anything arbitrarily rebound depending on where they land up’.