Question

I am using ruby 1.8.7 (2011-12-28 patchlevel 357) [i686-darwin11.2.0] I am playing with Kernet::eval method.

The binding may be a Binding object or a Proc object.
def eval(string, *binding_filename_lineno)
end

I have following test.rb

require 'pp'
p = proc { local_var = 'value'; puts "initializing local_var as #{local_var}" }
p.call()
pp eval("local_variables", p)

calling ruby test.rb prints

initializing local_var as value
["p"] 

Shouldn't this return ["p", "local_var"] ?

Also I see that in Ruby 1.9.3 eval only accepts a Binding object.

Was it helpful?

Solution

binding method defined on proc returns the binding the proc has been created in and do not care for the proc content.

require 'pp'
some_local_var = 'hello'
p = proc { local_var = 'value'; puts "initializing local_var as #{local_var}" }
p.call()
pp eval("local_variables", p) #=> ['p', 'some_local_var']

variable local_var lives only for the short time the proc is being executed. The fact you executed it before doesn't mean it is still out there (It might be if GC didn't run yet, but you shouldn't use it). In general you should treat it as unreachable. However, since proc has an access to it's own binding and it carries the binding with itself, you can trick it by declaring the inner variable outside of proc:

require 'pp'
local_var = 'hello'
p = proc { local_var = 'value'; puts "initializing local_var as #{local_var}" }
pp eval("local_var", p) #=> hello
p.call()
pp eval("local_var", p) #=> value

This can be used to create procs which behaves like objects:

def get_counter
  i = 0
  proc { p i+=1 }
end

a = get_counter
b = get_counter

5.times { a.call }   #=> 1 2 3 4 5
3.times { b.call }   #=> 1 2 3

eval('i', a)         #=> 5
eval('i', b)         #=> 3

3.times { a.call }   #=> 6 7 8

However it is not the best practice and is the main cause of most memory leaks in Ruby programs.

In Ruby 1.8.7 you could pass proc instead of binding, but eval was internally calling binding method on proc. This has been removed later on, so now you need to extract it explicitly.

pp eval("local_variables", p.binding)

UPDATE:

Since this is ruby, obviously we can do anything and naturally there is a way to access proc inner varaibles:

require 'pp'
proc_binding = nil;
p = proc { local_var = 'value'; puts "initializing local_var as #{local_var}"; proc_binding = binding }
p.call()
pp eval("local_variables", proc_binding) #=> ["proc_binding", "p", "local_var"]

Be cautious though - storing inner binding within a variable means that local_var is being referenced even after the proc runs and henced is not being collected by GC. In additions since the proc is referencing it and it has not been cleaned, it won't be redeclared locally which is pretty much a quick way to ruin your life. Those techniques might be useful in some very rare cases, but they will usually cause a lot of extremely hard to debug issues.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top