binding - how to explain it?

G

Giles Bowkett

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.
 
D

dblack

Hi --

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.

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

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
G

Giles Bowkett

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:
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?

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:

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:
When using eval, it can be helpful to state explicitly the context in
which the expression should be evaluated, rather than using the
current context. You can obtain a context by calling Kernel#binding
at the desired point.

def get_a_binding
val = 123
binding
end

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.
 
A

ara.t.howard

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/
 
A

ara.t.howard

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 ;-)

a @ http://drawohara.com/
 
G

Giles Bowkett

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 ;-)

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.
 
A

ara.t.howard

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/
 
G

Giles Bowkett

I think I'll see what happens if I make that change. do the tests blow
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.
 
E

Ezra Zygmuntowicz

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/


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 Zygmuntowicz
-- Founder & Ruby Hacker
-- (e-mail address removed)
-- Engine Yard, Serious Rails Hosting
-- (866) 518-YARD (9273)
 
M

Morton Goldberg

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?

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

<code>
a = 1

def x
eval(DATA.read, TOPLEVEL_BINDING)
end

x

__END__
puts a
</code>

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

x(42)

__END__
puts a
</code>

Regards, Morton
 
G

Giles Bowkett

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.)
 
D

dblack

Hi --

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

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
G

Giles Bowkett

defining context. But it seems to contradict what David Black posted
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.
 
D

dblack

Hi --

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?

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

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,264
Messages
2,571,315
Members
47,996
Latest member
LaurenFola

Latest Threads

Top