metaeducation / rebol-issues

6 stars 1 forks source link

BODY-OF strips a function body of any context. #2221

Open rebolbot opened 8 years ago

rebolbot commented 8 years ago

Submitted by: rgchris

Using BODY-OF to obtain the body of a function (deep) strips the context from the content of the resultant block. This precludes use of the body in any meaningful way, for example building a derivative function.

>> do foo: does [print "Foo"]
Foo
>> do foo: does body-of :foo
** Script error: print word is not bound to a context
** Where: function!
** Near: :foo

CC - Data [ Version: r3 master Type: Issue Platform: All Category: Native Reproduce: Always Fixed-in:none ]

rebolbot commented 8 years ago

Submitted by: fork

The reflector for functions was doing an Unbind_Block on the copy of the body before returning it. As you say, this makes the body fairly useless.

On a mechanical level, there is an argument against allowing words bound to function locals to "escape". In FUNCTION!s, the words are bound "stack relative"...which is to say that once you get them out of the function they're in, you can't get a value. (Unless that function is above you on the stack, in which case you'll get the value of the nearest invocation above you on the stack.) The argument against allowing them to escape for a CLOSURE! could be considered stronger, as the bound locals are actually just placeholders which can never be looked up (the body is copied and words rebound to a real frame).

But philosophically in metaprogramming, it seems you could well be interested in a distinction between unbound words in a function body, and those which were bound to function parameters. So it's worth thinking about how to get past the mechanical issues to offer that detection. But so long as BIND? (binding-of) can only return TRUE for any word bound to a function! local, you would be unable to tell the difference between a word bound to an inner function's locals or an outer function containing it.

foo: func [x] [
    bar: func [y] [
        x y
    ]
    body: body-of bar
    if (bind? body/1) == (bind? body/2) [
        print {Yup, they're both... locals to something.  But what?}
    ]
]

Being able to ask something like bind? body/1 above and get back the function value for foo is not something that's mechanically set up right now, but it could be done. Harder would be if these were closures, because with a closure the binding has to be to the instance of the context for that closure call. So this means you might be interested in binding to that closure instance OR getting to the archetypal closure to ask about its body. :-/ So there'd have to be a new type representing a closure instance... CLOSED! ? Anyway, something that you could bind to representing an instance as well as get access to its body.

Barring knowledge of what the right thing to do here is right this minute, Ren/C is taking the "don't introduce any new kinds of problems" approach. Today, closures cannot leak local references out of their body (with negative in-frame indices) because you never invoke a closure without its body being scanned and glued to an object created for that call. But a function can leak them just as easy as:

foo: func [x] [return [x]]

So what I've done is to unbind the closure's local references, and leave the ones in the function alone. It's a short-term answer but hopefully one that decreases pain for now. Result is like:

>> foo: function [x] [print x]
>> bind? second body-of :foo
== true
>> bar: closure [x] [print x]
>> bind? second body-of :bar
== none
https://github.com/metaeducation/ren-c/commit/5267b001dc6dec345a5d04fda43e1cce8dc0bb7a
rebolbot commented 8 years ago

Submitted by: Ladislav

Carl implemented this change and a couple of other limitations as a safety measure to restrict access to internals of "private" functions.

rebolbot commented 8 years ago

Submitted by: Ladislav

"On a mechanical level, there is an argument against allowing words bound to function locals to "escape"." - this is actually unrelated, the discussed measure "protects" other words in the function body as well.

"...so long as BIND? (binding-of) can only return TRUE for any word bound to a function! local, you would be unable to tell the difference between a word bound to an inner function's locals or an outer function containing it." - that is actually false, such a detection is possible.