Custom Function Generator Pitfalls

Overall this was very useful to see worked through!

The original point I started the thread to make is now made more convincingly in "What Dialects Need From Binding".

I don't know about "obvious", but...

...do take note that's how I described it, walking through the steps in "Rebol and Scopes: Why Not".

"The virtual bind chain starts out with a module context that has global, x, and foo in it. This is all there is to stick on the BLOCK!s that gets passed to FUNC. So the spec and body are blocks with a module as the specifier."

"FUNC stows the body block away in an ACTION! that it generates. Later when it gets invoked, it creates a FRAME! with return and x in it...and puts that in a chain with the module context. So upon entry to the function body, that body is being executed with a specifier that looks in the frame first (would find that x) and then in the module second (would find global and foo). This compound specifier is what the evaluator state initially has for that body block."

So the concept of quotes shielding material from binding under evaluation does seem to be a winner! Good instincts, there.

(Though as I mentioned, that too has come up before. See "Breaking MAKE OBJECT! Into Component Operations". I wasn't "all in" on it yet, due to the impacts it would have on existing programming style. But as this has unfolded, breaking the existing expectations seems inevitable if binding is going to become more hygeinic.)

But I think when all is taken into consideration here, you have to write it like:

return spread compose [
     (in [] 'let) (name): (in [] 'passthru) :return
 ]

Presumed rule is that the block COMPOSE produces at the end will have the same environment its argument came in with.

GROUP!s are evaluated using the environment of the outer block, while other material is left as-is.

Using LET means you don't have to UNBIND the name you get in, because we assume LET ignores the binding. But generally speaking, you probably can't trust that callers don't pass you bound words, even if that binding is not meaningful for the usage.

You want PASSTHRU with its definition in the function. Or you can compose in PASSTHRU as a FRAME! (I'd like to make that as friendly as possible for debugging... it does cache the word of the name it was fetched from, but still, it will never be quite as friendly as a word).

I've added that for LET as well, which is presumably needed here... and to be safe about the calling environment you could protect it this way, or trust it's not redefined and just say LET and let it be unbound, to pick up the binding in the splicd environment.

You probably don't need spread bindable because it probably ignores the outer binding on splices.
The implications of losing the binding on things you SPREAD are not entirely clear.

As these patterns become more understood, dialect tools should be able to help make the more common cases briefer. A complement to COMPOSE which assumes you want hardened references to the existing environment with relatively few "escaped" slots--what I was calling "COMPILE"--is probably useful.

2 Likes