我有这个代码:
l = lambda { a }
def some_function
a = 1
end
Run Code Online (Sandbox Code Playgroud)
我只想a通过lambda和一个特殊的范围进行访问,该范围a已经some_function在示例中已经定义过,或者很快就会在以下相同的范围内:
l = lambda { a }
a = 1
l.call
Run Code Online (Sandbox Code Playgroud)
然后我发现在调用时l,它仍然使用自己的绑定,但不是调用它的新绑定.
然后我试着用它作为:
l.instance_eval do
a = 1
call
end
Run Code Online (Sandbox Code Playgroud)
但这也失败了,奇怪的是我无法解释原因.
我知道其中一个解决方案是使用eval,其中我可以特别绑定并在文本中执行一些代码,但我真的不想这样使用.
而且,我知道它能够使用全局变量或实例变量.但是,实际上我的代码是在更深层次的嵌入式环境中,所以如果不是非常必要,我不想打破已完成的部分.
我Proc在文档中引用了这个类,我发现了一个binding引用了Proc上下文的函数名.虽然该函数仅提供了一种访问其绑定但无法更改它的方法,除了使用Binding#eval.它也评估文本,这正是我不喜欢做的.
现在的问题是,我有更好(或更优雅)的方式来实现这个吗?或者使用eval已经是常规方式?
编辑回复@Andrew:
好的,这是我在写一个词法解析器时遇到的一个问题,我在其中定义了一个包含固定数量项的数组,其中包括至少一个Proc和一个正则表达式.我的目的是匹配正则表达式并在我的特殊范围内执行Procs,其中Proce将涉及一些应在稍后定义的局部变量.然后我遇到了上面的问题.
其实我想这是不完全一样对这个问题,因为我的是如何通过在结合到一个Proc,而不是如何把它传递出去.
@Niklas:得到你的答案,我认为这正是我想要的.它完美地解决了我的问题.
Nik*_* B. 25
您可以尝试以下hack:
class Proc
def call_with_vars(vars, *args)
Struct.new(*vars.keys).new(*vars.values).instance_exec(*args, &self)
end
end
Run Code Online (Sandbox Code Playgroud)
要像这样使用:
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
Run Code Online (Sandbox Code Playgroud)
不过,这不是一个非常普遍的解决方案.如果我们可以给它Binding实例而不是哈希并且执行以下操作会更好:
l = lambda { |a| foo + a }
foo = 3
l.call_with_binding(binding, 1) # => 4
Run Code Online (Sandbox Code Playgroud)
使用以下更复杂的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
Run Code Online (Sandbox Code Playgroud)
基本上我们定义了一个手动名称查找堆栈和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)
Run Code Online (Sandbox Code Playgroud)
打印15,如预期:)
更新:Github现在也提供代码.我现在也将它用于我的一个项目.
| 归档时间: |
|
| 查看次数: |
5255 次 |
| 最近记录: |