在Ruby中更改Proc的绑定

时间:2012-04-07 22:49:46

标签: ruby

我有这段代码:

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

我只想通过lambda访问a和一个已经定义a的特殊范围,例如在示例中的some_function内部,或者稍后在同一范围内:

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

然后我发现在调用l时,它仍然使用自己的绑定,但不是调用它的新绑定。

然后我尝试将其用作:

 l.instance_eval do
     a = 1
     call
 end

但这也失败了,奇怪的是我无法解释原因。

我知道其中一个解决方案是使用eval,我可以在其中特殊地绑定并在文本中执行一些代码,但我真的不想这样使用。

并且,我知道它能够使用全局变量或实例变量。但是,实际上我的代码是在更深层次的嵌入式环境中,所以如果不是非常必要,我不想打破已完成的部分。

我在文档中引用了Proc类,我找到了一个引用binding上下文的函数名Proc。虽然该函数仅提供了一种访问其绑定但无法更改它的方法,但使用Binding#eval除外。它也评估文本,这正是我不喜欢做的。

现在问题是,我有更好(或更优雅)的方式来实现这个吗?或者使用eval已经是常规方式?

编辑回复@Andrew:
好吧,这是我在编写词法解析器时遇到的一个问题,我在其中定义了一个包含固定数量项的数组,其中包括至少一个Proc和一个正则表达式。我的目的是匹配正则表达式并在我的特殊范围内执行Procs,其中Proce将涉及一些应在稍后定义的局部变量。然后我遇到了上面的问题。
实际上我认为它与that question不完全相同,因为我的如何将传递绑定到Proc而不是如何传递 out

@Niklas: 得到你的答案,我认为这就是我想要的。它完美地解决了我的问题。

4 个答案:

答案 0 :(得分:23)

您可以尝试以下黑客攻击:

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

要像这样使用:

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

但这不是一个非常普遍的解决方案。如果我们可以给它Binding实例而不是哈希,那就更好了,并执行以下操作:

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

使用以下更复杂的hack,可以实现这种确切的行为:

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

基本上我们自己定义了一个手动名称查找堆栈,instance_exec我们对它进行了proc。这是一种非常灵活的机制。它不仅可以实现call_with_binding,还可以用于构建更复杂的查找链:

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)

这打印15,如预期的那样:)

更新:代码现在也可用at Github。我现在也将它用于我的一个项目。

答案 1 :(得分:2)

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

然后将其用作:

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

答案 2 :(得分:0)

类似的方式:

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

答案 3 :(得分:0)

也许您以后实际上不需要定义a,而只需要稍后对其进行设置

或者(如下所示),也许您实际上不需要a作为局部变量(其本身引用数组)。相反,也许您可​​以有用地使用类变量,例如@@a。通过打印“ 1”对我有用:

class SomeClass
  def l
    @l ||= lambda { puts @@a }
  end

  def some_function
    @@a = 1
    l.call
  end
end
SomeClass.new.some_function