ENVELOP (and COMPOSE!) By Example

I have realized that this is an incredibly useful ability...

...but even more importantly...

The Binding Aspect Motivates COMPOSE-by-Example

Since today's COMPOSE is arity-1, to get it to work at all you have to run it on a bound block (assuming the nested groups you're composing aren't somehow already bound). The tip of the binding of that block is what COMPOSE sloppily borrows to use when evaluating the inner groups.

>> x: 1, y: 2  ; let's say these are incidental definitions

>> var: 'y

>> code: compose '[x + (var)]
** Error: var is not bound

>> code: compose [x + (var)]  ; eval'd BLOCK! binds, compose borrows that binding
== [x + y]  ; but the result tip still has the binding

>> eval compose [let x: 10 let y: 20 (as group! code)]
== 3  ; let's say this is not what I meant

If you didn't want the final result of a COMPOSE to be unbound, you still have to bind the block long enough for compose to find the bindings...and then unbind it.

Not only is that awkward, what if you had a meaningful binding on the input you wanted to keep. You'd have to store the binding somehow... bind to the context for your groups long enough for the compose to work, then rebind it to the stored binding...

Compose-By-Example Can Fix This! :smiley:

Let's bring back an old term...and call it COMBINE.

>> code: combine $() '[x + (var)]
== [x + y]  ; worked even though we passed in an unbound block!

>> eval compose [let x: 10 let y: 20 (as group! code)]
== 30

So not only do you get the freedom to specify what delimiters (or synthetic/nested delimiters) you want to use, you can also supply an arbitrary binding.

Old COMPOSE Is Still Useful Day-To-Day

It's useful enough to keep its name, and do what it does. It works out a lot of the time.

But the strange thing here is that COMPOSE wouldn't just be a specialization of COMBINE with an unbound group '(). I think that would imply leaving the bindings on the groups as-is, not stealing the binding off of the other argument.

So COMPOSE would likely instead be an adaptation of COMBINE that would take the binding off of the thing you passed it, and put it onto the "example". Let's say the two arguments to COMBINE are PATTERN and TEMPLATE (see post on specialize:relax):

compose: adapt (specialize:relax get $combine [
    pattern: ~<removed from interface (ADAPT phase fills in)>~
]) [
    pattern: inside template '()
]

This is all quite cool. Agree, @bradrn?