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 Bowkett

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

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

···

On Fri, 3 Aug 2007, Giles Bowkett wrote:

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

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:

<macbook of doom:giles> [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:

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.

···

--
Giles Bowkett

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

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

···

On Aug 2, 2007, at 1:32 PM, dblack@rubypal.com wrote:

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?

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 Bowkett wrote:

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

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

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:09 PM, Giles Bowkett 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.

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

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 Bowkett

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

On 8/2/07, Morton Goldberg <m_goldberg@ameritech.net> wrote:

On Aug 2, 2007, at 1:32 PM, dblack@rubypal.com wrote:

> 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

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 Bowkett

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

Hi --

···

On Sat, 4 Aug 2007, Giles Bowkett 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

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

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/

···

On Aug 2, 2007, at 12:54 PM, Giles Bowkett 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.

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

> 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 Bowkett

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

> 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 Bowkett

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

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
-- ez@engineyard.com
-- Engine Yard, Serious Rails Hosting
-- (866) 518-YARD (9273)

···

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

On Aug 2, 2007, at 12:54 PM, Giles Bowkett 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/
--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama

Hi --

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?

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

···

On Sat, 4 Aug 2007, Giles Bowkett wrote:

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