3

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.

Usman
  • 2,325
  • 1
  • 23
  • 18

1 Answers1

2

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.

BroiSatse
  • 44,031
  • 8
  • 61
  • 86
  • Your example of "creating procs which behaves like objects" is probably what's called a closure. – Usman Apr 24 '14 at 17:32
  • Also I don't think in last example it stops you redeclaring the local_var. At least it works on my machine. – Usman Apr 24 '14 at 17:36
  • @Usman - What I meant is that if you call proc again, the `local_var` is declared even before it is assigned. In the examples provided it doesn't make any difference, but it will if you for example check its value with if. – BroiSatse Apr 24 '14 at 19:57