我有这段代码:
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: 得到你的答案,我认为这就是我想要的。它完美地解决了我的问题。
答案 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