Functions That Capture the Current "Evaluation Environment"

This sounds very Kernel-like, e.g.

foo: func [block <environment> env] [
    print mold block
    print [env.x env.y]
]

bar: func [x] [
   let y: 20
   foo [x + y]
]

>> bar 10
[x + y]
10 20

It's true that specifiers have been creeping toward being able to offer something like this functionality. But they were originally made with a particular mix of correctness and optimization in mind for combining FRAME! instances with "relativized" function bodies, so that simple index numbers could be used to find instance variables. Extending them has been experimental to permit things like LET but they're fairly brittle at present.

Today's specifiers narrow the fiddling to literals. e.g. if foo [x + y] would have the specifier influence [x + y], but if you'd written if foo (block) the specifier wouldn't affect the passed-in block...so it's different from a situation where IF was receiving an environment.


I'm worried about a generalized <environment> becoming available... and especially if such an out-of-band parameter sneaks in and affecting the behavior of fundamentals like COMPOSE or EITHER etc.

Once you have this out of band parameter, you have the problem of what happens when things get a step removed and you need to start making it passed in-band. (e.g. the difference between a function that calls COMPOSE directly vs. a function that calls a function that calls COMPOSE, where the intended environment of influence comes from the outermost call).

You also start getting some strange combinatorics of how much influence the environment is supposed to have in an operation vs. any existing binding on the passed in items. So I'd generally been going with the idea that the evolution of binding from being "just on words" to being "on words and arrays" would keep with the spirit that the way to tunnel "environments" through is to use mechanisms that put them granularly on your arguments vs. relying on this parameter.

For the vast majority of code, I like the idea that whatever is done with environments gets those environments pasted onto the values themselves, so that things like GET and COMPOSE remain single-arity in an abstractable way, so that they can also be abstracted as single-arity functions without introducing quandaries from sometimes-implicit environment parameters.

Under the design being proposed at time of writing, it seems you could just say:

in [] 'some-var

And that would effectively be BIND-TO-CURRENT. It's brief, and can be argued as clear enough, if you've grokked the model.

It could also be considered important enough to take another evaluator behavior, saying e.g. that . is an empty TUPLE! and means "get the current environment":

in . 'some-var

But that may be wasteful considering what else . might mean, if [] is good enough.

(actually, if going with my proposal for using .member to denote member references in methods, then . might make more sense as "this"/"self")

An example of a place where access to the evaluator's notion of the current environment is needed in particular is the implementation of things like LET, which want to be able to add to the environment and affect the wave of evaluation going forward.

Right now that's a specific narrow API that just says "extend binding of frame" without telling you what the binding was.

Exercising a lot of restraint on implicit parameterization to achieve core functions is something I'll advocate for, but it may become inevitable that some functions need the environment without having it passed explicitly. I want to see how far things can get without it.

But this does mean that you wouldn't be able to use COMPOSE on quoted blocks:

>> compose '[a (add 1 2) b]
** Error: ADD is not bound

I'd like to see answers to that which don't make COMPOSE pick up an environment implicitly. One would be to use a regular block during the composition, then unbind it (or whatever operator does a shallow unbinding of the "tip", I suggested BINDABLE as another alternative):

>> unbind compose [a (add 1 2) b]
== [a 3 b]  ; with A and B unbound

But there's also COMPOSE/PREDICATE where you get the GROUP!s to operate on and can do whatever you want with them.

>> compose/predicate '[a (add 1 2) b] (group -> [do in [] group])
== [a 3 b]  ; with A and B unbound

Although that weirdly would make the GROUP local variable in the lambda part of the evaluation environment of the (add 1 2). So... hm. It does start making you wonder if do in .. group to ask to sutbtract a level of binding would be a reasonable approach--e.g. "give me the environment of the [do in .. group] block before it had the function locals added."

Anyway, I just wanted to reiterate my hope that this isn't done with a sneaky <environment> argument, which I fear would muddy things considerably.