在 Ruby 中更改 Proc 的绑定 [英] Change the binding of a Proc in Ruby

查看:28
本文介绍了在 Ruby 中更改 Proc 的绑定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个代码:

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

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

I just want to access a by the lambda and a special scope which has defined a already somewhere like inside some_function in the example, or just soon later in the same scope as:

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

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

Then I found when calling l, it is still using its own binding but not the new one where it was called.

然后我尝试将其用作:

 l.instance_eval do
     a = 1
     call
 end

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

But this also failed, it is strange that I can't explain why.

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

I know the one of the solution is using eval, in which I could special a binding and executing some code in text, but I really do not want to use as so.

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

And, I know it is able to use a global variable or instance variable. However, actually my code is in a deeper embedded environment, so I don't want to break the completed parts if not quite necessary.

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

I have referred the Proc class in the documentation, and I found a function names binding that referred to the Proc's context. While the function only provided a way to access its binding but cannot change it, except using Binding#eval. It evaluate text also, which is exactly what I don't like to do.

现在的问题是,我是否有更好(或更优雅)的方法来实现这一点?或者使用 eval 已经是常规方式?

Now the question is, do I have a better (or more elegant) way to implement this? Or using eval is already the regular manner?

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

Edit to reply to @Andrew:
Okay, this is a problem which I met when I'm writing a lexical parser, in which I defined a array with fixed-number of items, there including at least a Proc and a regular expression. My purpose is to matching the regular expressions and execute the Procs under my special scope, where the Proce will involved some local variables that should be defined later. And then I met the problem above.
Actually I suppose it is not same completely to that question, as mine is how to pass in binding to a Proc rather than how to pass it out.

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

@Niklas: Got your answer, I think that is what exactly I want. It has solved my problem perfectly.

推荐答案

您可以尝试以下 hack:

You can try the following 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 并执行以下操作,那就更好了:

This is not a very general solution, though. It would be better if we could give it Binding instance instead of a Hash and do the following:

l = lambda { |a| foo + a }
foo = 3
l.call_with_binding(binding, 1)  # => 4

使用以下更复杂的 hack,可以实现这种确切的行为:

Using the following, more complex hack, this exact behaviour can be achieved:

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,还可以用来构建更复杂的查找链:

Basically we define ourselves a manual name lookup stack and instance_exec our proc against it. This is a very flexible mechanism. It not only enables the implementation of call_with_binding, it can also be used to build up much more complex lookup chains:

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,正如预期的那样:)

This prints 15, as expected :)

更新:代码现在也可在在 Github.我现在也在我的一个项目中使用它.

UPDATE: Code is now also available at Github. I use this for one my projects too now.

这篇关于在 Ruby 中更改 Proc 的绑定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆