在Ruby中更改Proc的绑定

我有这个代码:

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

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

  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将涉及一些应在稍后定义的局部变量。 然后我遇到了上面的问题。
实际上我认为它与那个问题完全不同,因为我的如何将绑定传递给Proc而不是如何将其传递出去

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

您可以尝试以下hack:

 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实例而不是Hash会更好并执行以下操作:

 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,如预期:)

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

 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