Binding - how to explain it?

There was a thread here recently about this idiom in Rails:

eval(IO.read(file), binding, file)

I changed it to a method:

def eval_file_with_binding(file)
eval(IO.read(file), binding, file)
end

It appeared twice in Rails, so refactoring it was kind of overkill,
but I have code OCD. Plus, the thread on this list came from somebody
trying to puzzle out what the code did, and it’s very repetitive code,
so I wanted to make it simpler and clearer.

Anyway, the tests pass, but the patch hasn’t been applied, because
there’s a question raised, which is why does it send the correct
binding when it’s defined in some other method. On the one hand the
answer seems obvious, because it’s a method on Kernel, but honestly I
can’t understand it as well as I think I do, because I can’t explain
it beyond that “obvious” point. How do I explain how this code works?
How does this code work? I know it works, but I can’t actually explain
it.


Giles B.

Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org

Hi –

On Fri, 3 Aug 2007, Giles B. wrote:

It appeared twice in Rails, so refactoring it was kind of overkill,
How does this code work? I know it works, but I can’t actually explain
it.

Can you produce a complete working example? I’m having trouble
getting it not to not work… Here’s what I’m doing so far:

a = 1

def x
eval(DATA.read, binding)
end

x # error: a undefined

END
puts a

Is that analogous to what’s in the original?

David

Can you produce a complete working example? I’m having trouble
getting it not to not work… Here’s what I’m doing so far:

If you have edge Rails, you can use this diff:

http://dev.rubyonrails.org/attachment/ticket/9128/eval_file_etc.diff

And then run these tests:

railties/test/plugin_load_test.rb
railties/test/initializer_test.rb

In terms of the code in your mail:

Is that analogous to what’s in the original?

I think it is, and I can’t get that to run either. But then why do the
tests pass?

I’m doing something very similar:

[08-02 10:58] ~
! irb

def eval_var_with_binding(var)
eval(var, binding)
end
=> nil
a = :xyz
=> :xyz
eval_var_with_binding(“puts a”)
NameError: undefined local variable or method a' for #<Object:0x349f4> from (irb):2:in eval_var_with_binding’
from (irb):2:in `eval_var_with_binding’
from (irb):5

The only thing that’s different is the third arg to eval(). e.g.:

eval(IO.read(file), binding, file)

vs.

eval(var, binding)

But it turns out that passing the filename as a third arg only affects
error reporting, according to the Pickaxe.

Here’s something else from the Pickaxe:

val = “cat”
the_binding = get_a_binding

eval(“val”, the_binding) # 123
eval(“val”) # “cat”

The first eval evaluates val in the context of the binding as it
was as the method get_a_binding was executing. In this binding,
the variable val had a value of 123. The second eval evaluates
val in the toplevel binding, where it has the value “cat”.

If the tests are passing but the binding doesn’t operate as I expected
it to, I think this may mean that the binding method is entirely
unnecssary at this point, and only passed at all so that you can also
pass the third arg and get the file-specific error-reporting.


Giles B.

Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org

On Aug 2, 2007, at 12:09 PM, Giles B. wrote:

I think it is, and I can’t get that to run either. But then why do the
tests pass?

the obvious answer is that the original code did nothing. i’ve
looked at that very code in the rails base and my perception, as with
a lot of the rails base, is that it’s superfluous and simply cruft
left over from something. i could easily be wrong - but the fact
that your tests are passing is a strong indicator…

cheers.

a @ http://drawohara.com/

On Aug 2, 2007, at 12:09 PM, Giles B. wrote:

If the tests are passing but the binding doesn’t operate as I expected
it to, I think this may mean that the binding method is entirely
unnecssary at this point, and only passed at all so that you can also
pass the third arg and get the file-specific error-reporting.

i think that’s correct - at least that’s how i read it a few weeks
ago. what’s really interesting then is that

eval IO.read(path), Kernel.binding, path

is pretty much exactly

load path

only so much more exotic :wink:

a @ http://drawohara.com/

On Aug 2, 2007, at 12:54 PM, Giles B. wrote:

I think I’ll see what happens if I make that change. do the tests blow
up, or is it really that simple? stay tuned for the next exciting
adventure blah blah blah.

yeah - it’s surely complicated by the fact that rails has overidden
Kernel.load too… i’ll be interested to see what happens…

a @ http://drawohara.com/

I think I’ll see what happens if I make that change. do the tests blow
up, or is it really that simple? stay tuned for the next exciting
adventure blah blah blah.

yeah - it’s surely complicated by the fact that rails has overidden
Kernel.load too… i’ll be interested to see what happens…

Hmm, I didn’t know that. The initializer test passes but the plugin
loader test explodes.


Giles B.

Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org

ago. what’s really interesting then is that

eval IO.read(path), Kernel.binding, path

is pretty much exactly

load path

only so much more exotic :wink:

I think I’ll see what happens if I make that change. do the tests blow
up, or is it really that simple? stay tuned for the next exciting
adventure blah blah blah.


Giles B.

Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org

On Aug 2, 2007, at 1:32 PM, [email protected] wrote:

END
puts a

Is that analogous to what’s in the original?

I’m kind of late to this party, but is it relevant that each of the
following works?

a = 1

def x
eval(DATA.read, TOPLEVEL_BINDING)
end

x

END
puts a

def x(a) eval(DATA.read, binding) end

x(42)

END
puts a

Regards, Morton

Hi Morton - actually, that is entirely relevant. I think it indicates
that you get the binding from the calling context, rather than the
defining context. But it seems to contradict what David Black posted
earlier, and I saw the same thing in my own irb.

I posted Ezra’s version in the Rails ticket, but I think the whole
thing’s gotten so confusing that the ticket might not necessarily go
anywhere. :-\

It’s a pity, I think refactoring Rails is a pretty worthwhile task. I
think they took out a lot of the method_missing stuff because it was
too resource-intensive. (Actually that reminds me of an error message
I wanted to change.)


Giles B.

Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org

Hi –

On Sat, 4 Aug 2007, Giles B. wrote:

Hi Morton - actually, that is entirely relevant. I think it indicates
that you get the binding from the calling context, rather than the
defining context. But it seems to contradict what David Black posted
earlier, and I saw the same thing in my own irb.

I don’t think it contradicts my example; I’m evaluating “puts a”
inside a method where a has not been defined. Morton’s examples
either use the top-level binding explicitly, or set an a variable in
the method. My example doesn’t do either, so the top-level assignment
to a is essentially playing no role.

David

On Aug 2, 2007, at 12:27 PM, ara.t.howard wrote:

a @ http://drawohara.com/

we can deny everything, except that we have the possibility of
being better. simply reflect on that.
h.h. the 14th dalai lama

Rails uses that eval(IO.read(…), binding) on the init.rb of
plugins so that you have the config local variable defined so you can
use it just like you use config.blah = whatever in the
Rails::Initializer block in environment.rb. As far as I know that’s
the only reason it exists.

So when you wrap that call in a method without passing in the
binding, you defeat the purpose of this hack. If you did this instead
the tests will still pass and the right binding will be used:

Change this:

def eval_file_with_binding(file)
eval(IO.read(file), binding, file)
end

To this:

def eval_file_with_binding(file, bind=binding)
eval(IO.read(file), bind, file)
end

Cheers-
– Ezra Z.
– Founder & Ruby Hacker
[email protected]
– Engine Y., Serious Rails Hosting
– (866) 518-YARD (9273)

defining context. But it seems to contradict what David Black posted
earlier, and I saw the same thing in my own irb.

I don’t think it contradicts my example; I’m evaluating “puts a”
inside a method where a has not been defined. Morton’s examples
either use the top-level binding explicitly, or set an a variable in
the method. My example doesn’t do either, so the top-level assignment
to a is essentially playing no role.

but why in Morton’s code does the method’s call to binding result in
the same binding as TOPLEVEL_BINDING, when in your code calling
binding from a method doesn’t result in getting the var a which seems
to live in the top-level binding?

Basically I had a conversation with Koz from Rails core that went like
this:

Giles: It’s a good patch!
Koz: Why?
Giles: The tests pass!
Koz: Why do the tests pass?
Giles: Uhh…

So, having been stumped so easily, I’m still working on that.


Giles B.

Blog: http://gilesbowkett.blogspot.com
Portfolio: http://www.gilesgoatboy.org

Hi –

On Sat, 4 Aug 2007, Giles B. wrote:

the same binding as TOPLEVEL_BINDING, when in your code calling
binding from a method doesn’t result in getting the var a which seems
to live in the top-level binding?

It doesn’t; there’s a parameter ‘a’. Here’s Morton’s example again:

def x(a)
eval(DATA.read, binding)
end

x(42)

END
puts a

Inside the method, a is set to 42, and in the context of that binding,
“puts a” is executed.

David