Block Creation Vs. Evaluation

It's interesting that here, when the code being composed is under mostly the same understandings (e.g. of what PRINT and Y means), then unbound code could be used.

repeat 2 [
    code: '(print [x + y])  ; assuming quote evauations don't bind
    loop-five x compose [
       if x = 3 [continue]
       (code)
    ]
]

Where you wind up not being able to do this is when the place doing the composing is in some other library, e.g. where PRINT could mean something totally different. But worth pointing out that some number of places would be able to use unbound code. How many? Don't know.

I agree there are good arguments for why it may be a better way to bias the defaults. But holepunching is definitely going to come up. Perhaps I should have written:

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

I've talked a bit about Rebol's "blocks as currency" model, compared with "functions as currency", in terms of what that value proposition is:

Binding Re-Examined from First Principles

And I've wondered about a "parameterized block" abstraction which could leave slots in blocks ready to pick up enclosing bindings--while not making the structure opaque as a function would... which I guess would be like the UNUSE above:

code: unuse [x] [print [x + y]]

One implementation could be to deep copy the material... harden the bindings of all the words, while unbinding the arrays that contain them. The danger I mentioned is that stray unbound things could be bound accidentally when they should have been errors. Perhaps a "dead binding" would need to be given to any unbound things which the unuse didn't mention, to make sure those things were only examined literally... but, then you'd not be able to apply more than one UNUSE to code. :frowning: So maybe there's a specific "unused binding" that means "intentionally unbound to pick up enclosing binding" that is distinct from the default unbound, and then dead bindings distinct from that.

That could wind up being a lot of copying. Avoiding that and getting something more like today, then UNUSE would generate "holepunch" instructions that splice into the specifier. The system is required to do binding coalescing implicitly again, but biased to only do it on these called out structures for the words they specify. This would be a fairly complex mechanic, plus abstractions like FOR-EACH or FUNC would be encountering a more baffling data structure for the environment...if that structure had to account for persistent holepunch instructions. It may not be as bad as it seems, and perhaps not as bad as the cycles arising from the automatic 'overbinding' coalescing (though I think coalescing holes is likely overall harder).

Whatever happens, people who expect to do surgery on environments they find in blocks will face some hassles. If blocks are losing their environments in order to become candidates for binding to descend into them again, that loss may affect expectations that the environment would be available.

Anyway... aiming in this direction does have more formalism, and may avoid the seemingly random effects that spliced code can pick up. The method I've generally used with new ideas is just to prototype it and see what (of the tons of wacky corpus) breaks. Here that might be done with the deep copy method first for UNUSE, to generate points of discussion.