Change the binding of a Proc in Ruby

后端 未结 4 1496
春和景丽
春和景丽 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:26

    a similar way:

    class Context
      attr_reader :_previous, :_arguments
    
      def initialize(_previous, _arguments)
        @_previous = _previous
        @_arguments = _arguments
      end
    end
    
    def _code_def(_previous, _arguments = [], &_block)
      define_method("_code_#{_previous}") do |_method_previous, _method_arguments = []|
        Context.new(_method_previous, _method_arguments).instance_eval(&_block)
      end
    end
    
    _code_def('something') do
      puts _previous
      puts _arguments
    end
    
    0 讨论(0)
  • 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.

    0 讨论(0)
  • 2020-12-15 06:46
    class Proc
        def call_with_obj(obj, *args)
            m = nil
            p = self
            Object.class_eval do
                define_method :a_temp_method_name, &p
                m = instance_method :a_temp_method_name; remove_method :a_temp_method_name
            end
            m.bind(obj).call(*args)
        end
    end
    

    And then use it as:

    class Foo
        def bar
            "bar"
        end
    end
    
    p = Proc.new { bar }
    
    bar = "baz"
    
    p.call_with_obj(self) # => baz
    p.call_with_obj(Foo.new) # => bar
    
    0 讨论(0)
  • 2020-12-15 06:47

    Perhaps you don't actually need to define a later, but instead only need to set it later.

    Or (as below), perhaps you don't actually need a to be a local variable (which itself references an array). Instead, perhaps you can usefully employ a class variable, such as @@a. This works for me, by printing "1":

    class SomeClass
      def l
        @l ||= lambda { puts @@a }
      end
    
      def some_function
        @@a = 1
        l.call
      end
    end
    SomeClass.new.some_function
    
    0 讨论(0)
提交回复
热议问题