Change the binding of a Proc in Ruby

后端 未结 4 1506
春和景丽
春和景丽 2020-12-15 05:48

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 sco

4条回答
  •  臣服心动
    2020-12-15 06:46

    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.

提交回复
热议问题