I wrote up a bit on how COLLECT and KEEP are implemented, where the "keeper" function is passed as an argument named KEEP to a function that has been built out of the collect body.
Historical Rebol's idea of binding in functions is that regardless of where the contents of a function body came from, any arguments or locals will be deeply bound... overriding previous bindings of the word.
So even in examples of a deep composition like this...
keep: does [print "Outer KEEP"]
code: [keep <example>]
collect compose/deep [
repeat 1 [
repeat 1 [
repeat 1 (code)
]
]
]
...the KEEP composed into the collect body will find the KEEPER supplied by the COLLECT, not the function defined on the top line.
Questions raise about how it is possible to know that this is what was desired. Preserving the original KEEP's meaning is another valid intent... and arguably the more conservative intent for composition.
Biasing Toward Binding Preservation
@bradrn has suggested that if a block is bound, then KEEP would only be overridden in the topmost level. The reason it would work would be due to a single-step of surgery done to merge the function's variables into the environment of the lambda's body block:
keep: does [print "Outer KEEP"]
code: [keep <example>]
collect code ; would generate [<example>] block
The proposal is that it only works because the KEEP inside of CODE is actually left unbound. The CODE block itself has a binding on its tip, bestowed upon it when the evaluator passed over it...but this did not descend deeply.
You'd get a different outcome if you bound KEEP itself. Hypothetical code that would do that:
keep: does [print "Outer KEEP"]
code: compose [(bind-to-current 'keep) <example>]
collect code ; would print "Outer KEEP"
Also, the evaluator would defer to the bindings of blocks or groups found deeper than the topmost level. They would not get the override:
keep: does [print "Outer KEEP"]
code: [keep <example>]
collect compose [(as group! code)] ; would print "Outer KEEP"
If you wanted to get already bound items to see the COLLECT's KEEP, you would need to "punch holes" in the binding. For the moment, let's call the hole-punching construct UNUSE:
keep: does [print "Outer KEEP"]
code: [keep <example>]
collect compose [(unuse [keep] as group! code)] ; gathers [<example>]
Why One Level of Binding, Not Zero?
It might seem a little random to "override KEEP for one level of depth". Why not always need UNUSE if working with bound material?
keep: does [print "Outer KEEP"]
code: [keep <example>]
collect code ; would print "Outer KEEP"
keep: does [print "Outer KEEP"]
code: [keep <example>]
collect unuse [keep] code ; would generate [<example>] block
The problem here is that the operating suggestion is that code is unbound by default, but blocks capture a binding in their environment under evaluation (again, just at the tip). So ordinarily literal code like the following wouldn't work (nor would a lot of other things, like basic function definitions):
keep: does [print "Outer KEEP"]
collect [keep <example>] ; would print "Outer KEEP"
So the concept of things like COLLECT (via LAMBDA) or FOR-EACH performing "environment surgery" at only the topmost level of a received block is mechanically tied to the notion of a common currency of blocks that have bindings at the tip, but are unbound otherwise. These would be the most frequently dealt with blocks that are received. The burden passes to those doing COMPOSE-like operations to be explicit about what holes they punch in the code they use, otherwise the default assumption is that all bindings will be interpreted as is.
In this line of thinking it isn't the shallow one-level-step of merging a tip-binding that is "weird", it's the person composing in already-bound code that wants to give up some of its bindings. So the burden is shifted to the composer.
An Alternative To UNUSE of Bound Code: Unbound Code
Today all arrays get bound (and incorporate "overbind" instructions, like LETs). But another proposal from @bradrn is that quoted code not add any binding under evaluation. As source code starts out unbound, this would make it easier to create a code fragment that picks up all of its meaning as if it had been written where it is being composed:
keep: does [print "Outer KEEP"]
code: '[keep <example>] ; bypasses evaluator binding "tip" of block
collect compose/deep [
repeat 1 [
repeat 1 [
repeat 1 (code)
]
]
] ; would generate [<example] block
This wouldn't be appropriate for something like passing a block of code to a function generator in a library. But it could let some situations avoid needing to do an annoying amount of UNUSE-ing.
Note that this would apply to all quoted things, including quoted WORD!s. This would make it harder to accidentally put stray bindings into things, meaning you'd inherit the default interpretation of a block:
keep: does [print "Outer KEEP"]
code: [] ; tip binds current evaluative context
collect [
append code 'keep ; unbound word inserted into bound block
append code <example>
do code ; would print "Outer Keep"
]
This would reduce stray bindings in the system significantly, which is a good thing.