Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change the binding of a Proc in Ruby

Tags:

I have this code:

 l = lambda { a }  def some_function      a = 1  end 

I just want to access a by the lambda and a special scope which has defined a already somewhere like inside some_function in the example, or just soon later in the same scope as:

 l = lambda { a }  a = 1  l.call 

Then I found when calling l, it is still using its own binding but not the new one where it was called.

And then I tried to use it as:

 l.instance_eval do      a = 1      call  end 

But this also failed, it is strange that I can't explain why.

I know the one of the solution is using eval, in which I could special a binding and executing some code in text, but I really do not want to use as so.

And, I know it is able to use a global variable or instance variable. However, actually my code is in a deeper embedded environment, so I don't want to break the completed parts if not quite necessary.

I have referred the Proc class in the documentation, and I found a function names binding that referred to the Proc's context. While the function only provided a way to access its binding but cannot change it, except using Binding#eval. It evaluate text also, which is exactly what I don't like to do.

Now the question is, do I have a better (or more elegant) way to implement this? Or using eval is already the regular manner?

Edit to reply to @Andrew:
Okay, this is a problem which I met when I'm writing a lexical parser, in which I defined a array with fixed-number of items, there including at least a Proc and a regular expression. My purpose is to matching the regular expressions and execute the Procs under my special scope, where the Proce will involved some local variables that should be defined later. And then I met the problem above.
Actually I suppose it is not same completely to that question, as mine is how to pass in binding to a Proc rather than how to pass it out.

@Niklas: Got your answer, I think that is what exactly I want. It has solved my problem perfectly.

like image 656
Shou Ya Avatar asked Apr 07 '12 22:04

Shou Ya


People also ask

What is the difference between a proc and a lambda?

There are only two main differences. First, a lambda checks the number of arguments passed to it, while a proc does not. This means that a lambda will throw an error if you pass it the wrong number of arguments, whereas a proc will ignore unexpected arguments and assign nil to any that are missing.

What is Binding in Ruby?

To keep track of the current scope, Ruby uses bindings, which encapsulate the execution context at each position in the code. The binding method returns a Binding object which describes the bindings at the current position.

What is the difference between procs and blocks?

Procs are objects, blocks are notA proc (notice the lowercase p) is an instance of the Proc class. This lets us call methods on it and assign it to variables. Procs can also return themselves. In contrast, a block is just part of the syntax of a method call.


Video Answer


1 Answers

You can try the following hack:

class Proc   def call_with_vars(vars, *args)     Struct.new(*vars.keys).new(*vars.values).instance_exec(*args, &self)   end end 

To be used like this:

irb(main):001:0* lambda { foo }.call_with_vars(:foo => 3) => 3 irb(main):002:0> lambda { |a| foo + a }.call_with_vars({:foo => 3}, 1) => 4 

This is not a very general solution, though. It would be better if we could give it Binding instance instead of a Hash and do the following:

l = lambda { |a| foo + a } foo = 3 l.call_with_binding(binding, 1)  # => 4 

Using the following, more complex hack, this exact behaviour can be achieved:

class LookupStack   def initialize(bindings = [])     @bindings = bindings   end    def method_missing(m, *args)     @bindings.reverse_each do |bind|       begin         method = eval("method(%s)" % m.inspect, bind)       rescue NameError       else         return method.call(*args)       end       begin         value = eval(m.to_s, bind)         return value       rescue NameError       end     end     raise NoMethodError   end    def push_binding(bind)     @bindings.push bind   end    def push_instance(obj)     @bindings.push obj.instance_eval { binding }   end    def push_hash(vars)     push_instance Struct.new(*vars.keys).new(*vars.values)   end    def run_proc(p, *args)     instance_exec(*args, &p)   end end  class Proc   def call_with_binding(bind, *args)     LookupStack.new([bind]).run_proc(self, *args)   end end 

Basically we define ourselves a manual name lookup stack and instance_exec our proc against it. This is a very flexible mechanism. It not only enables the implementation of call_with_binding, it can also be used to build up much more complex lookup chains:

l = lambda { |a| local + func(2) + some_method(1) + var + a }  local = 1 def func(x) x end  class Foo < Struct.new(:add)   def some_method(x) x + add end end  stack = LookupStack.new stack.push_binding(binding) stack.push_instance(Foo.new(2)) stack.push_hash(:var => 4)  p stack.run_proc(l, 5) 

This prints 15, as expected :)

UPDATE: Code is now also available at Github. I use this for one my projects too now.

like image 58
Niklas B. Avatar answered Nov 15 '22 09:11

Niklas B.