21

我有这个代码:

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

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

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

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

然后我尝试将其用作:

 l.instance_eval do
     a = 1
     call
 end

但这也失败了,奇怪的是我无法解释为什么。

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

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

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

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

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

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

4

4 回答 4

26

您可以尝试以下技巧:

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 :)

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

于 2012-04-07T23:29:24.673 回答
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
于 2012-05-19T23:42:28.143 回答
1

也许您实际上不需要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
于 2019-10-25T16:35:37.990 回答
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
于 2019-05-16T12:44:16.363 回答