【问题标题】:Change the binding of a Proc in Ruby在 Ruby 中更改 Proc 的绑定
【发布时间】:2012-04-07 22:49:45
【问题描述】:

我有这个代码:

 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 类,并且我找到了引用Proc 上下文的函数名称binding。虽然该函数仅提供了一种访问其绑定但不能更改它的方法,但使用 Binding#eval 除外。它还评估文本,这正是我不喜欢做的。

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

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

@尼克拉斯: 得到你的答案,我想这正是我想要的。完美解决了我的问题。

【问题讨论】:

  • 也许一个很好的问题是为什么你想这样做,或者更确切地说,你想要最终实现什么?
  • @Andrew:我不知道。我对这个确切问题的答案感兴趣,而不是对潜在问题的解决方案:)
  • @NiklasB。我同意,但了解最终目标有助于更好地理解问题。而且我很确定我不久前回答了类似的问题,我正在尝试找到它但找不到:(
  • @Andrew:我对此非常感兴趣!碰巧,我今天遇到了一个与动态绑定非常相似的问题。
  • @NiklasB。啊,这似乎与此相反:尝试在块中定义的方法中使用变量。 "Is it possible to access block's scope in method?"(哦,看看第一条评论你知道什么……似曾相识!)

标签: ruby


【解决方案1】:

您可以尝试以下技巧:

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。我现在也将它用于我的一个项目。

【讨论】:

  • 轻微挑剔:你的意思是lambda { |a| local + func + some_method + a }?非常酷的答案!
  • @Brandan:是的,我刚才也注意到了。固定的。谢谢!
  • @NiklasB。嘿朋友,我找到了更好更优雅的解决方案,请看下面的代码!
  • 这不会改变 self 所指的内容吗?如果有人创建了一个依赖于 self 不被更改的 proc,那么这将导致问题。
  • 我不会说它在所有情况下都是给定的。
【解决方案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

【讨论】:

  • 有什么意义?这些可以写成instance_exec(&amp;p)Foo.new.instance_exec(&amp;p)。我认为您只是重新发明了轮子..我的回答本来应该比这更笼统,但是如果instance_exec足够了,那就去吧:)使用临时方法的方法并不是特别优雅,因为它不是可重入(如,不能嵌套使用,也不能跨线程使用)。
  • 所以简而言之,我不认为这很“酷”,但如果它适合你,那就太好了:)
  • @NiklasB。哦,你是对的。我以前只是考虑是否可以摆脱字符串评估。阅读您的答案后,我认识了 instance_exec,但是……我只是在重新发明轮子。
【解决方案3】:

也许您实际上不需要稍后定义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

【讨论】:

    【解决方案4】:

    类似的方式:

    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
    

    【讨论】:

      猜你喜欢
      • 2011-08-16
      • 2012-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-24
      • 2012-09-24
      • 1970-01-01
      • 2012-01-18
      相关资源
      最近更新 更多