Initially, I resisted the idea of constructs that were sensitive to the binding in effect at the callsite.
Eventually I decided that COMPOSE needed to be such a construct.
It may seem that was in order to make it possible to do interpolation on strings, without having to say that strings themselves carry binding:
>> word: "this"
>> compose "Wanted (word) to work"
== "Wanted this to work"
But it turned out there was another motivation: that as unbound material was becoming an important currency, you frequently wouldn't even want BLOCK!s and such to carry bindings, you'd just want to compose inside them:
>> compose '[raw material (10 + 20) but you need to find plus]
== [raw material 30 but you need to find plus] ; unbound
This realization--that it's very frequent to want the binding of the templated code to be different from the evaluations you embed inside it--pushed me over the edge to believing COMPOSE had to be the kind of construct that could be aware of the current binding context at the callsite. It just happened to also facilitate easier string interpolation, which sealed the deal.
But then, how do you slipstream another context into the mix?
Well, you could say there's a COMPOSE:INSIDE or something like that (let's imagine we're using the new proposed ability where :INSIDE can put its argument before and not after)...
>> obj: make object! [add: subtract/]
>> compose:inside obj '[foo (add 10 20) bar]
== [foo -10 bar] ; unbound
That's one idea. But it means that every function sensing the binding context at the callsite would need such a refinement.
You might think of manually binding and evaluate your expression from scratch:
>> code: bind obj '[compose '[foo (add 10 20) bar]]
== [compose '[foo (add 10 20) bar]] ; bound
>> eval code
** PANIC: compose is unbound
Ooops. Can't find COMPOSE now. You'd need to get more clever than that, injecting the compose from outside into the code block to act as the operation:
>> code: bind obj compose '[($compose) '[foo (add 10 20) bar]]
== [compose '[foo (add 10 20) bar]]
>> eval code
== [foo -10 bar] ; unbound
Awkward as that is, it's actually rather cool that "binding science" is starting to actually have answers for these kinds of things. ![]()
But clearly there should be an easier way of doing that. One idea would be to say that it's an argument to APPLY:
>> apply:inside obj compose/ ['[foo (add 10 20) bar]]
== [foo -10 bar] ; unbound
That seems to give you the right granularity for the intent, and isn't a terrible idea.
Wild Brainstorm: What if "INSIDE" is like a REFRAMER
Remember that reframers work like:
>> two-times: reframer func [f [frame!]] [eval f, eval f]
>> two-times append [a b c] <d>
== [a b c <d> <d>]
Let's forget historical meanings of INSIDE, and use our imagination with:
>> inside obj compose '[foo (add 10 20) bar]
== [foo -10 bar] ; unbound
That breaks expectations a bit (as reframers do), but it's certainly less awkward compared with:
>> apply:inside obj compose/ ['[foo (add 10 20) bar]]
== [foo -10 bar] ; unbound
If you don't mind getting even weirder, INSIDE could be infix like the // operator, so COMPOSE would come first:
>> compose inside obj '[foo (add 10 20) bar]
== [foo -10 bar] ; unbound
But this would require people to read that as:
compose-inside obj '[foo (add 10 20) bar]
Not:
compose (inside obj '[foo (add 10 20) bar])
Too weird, or cool?
Other Ideas: EVAL Auto-Escaping
I pointed out the clumsiness of:
>> eval bind obj compose '[($compose) '[foo (add 10 20) bar]]
== [foo -10 bar] ; unbound
What if there was something less clumsy encapsulating this idea, such as:
>> weirdeval bind obj '[/compose '[foo (add 10 20) bar]]
== [foo -10 bar] ; unbound
Maybe any function calls denoted with /XXX would be assumed to use the binding of the outer scope?
I dunno. There are ideas here. But I think the key is I don't think every callsite-context-dependent function should need to have an :INSIDE refinement.