ruby - 使用 block 在父作用域中设置变量

标签 ruby metaprogramming

gem binding_of_caller有一个有关如何在父作用域中设置变量的示例:

(这只是从他们的自述文件中粘贴的)

def a
  var = 10
  b
  puts var
end

def b
  c
end

def c
  binding.of_caller(2).eval('var = :hello')
end

a()

# OUTPUT
# => hello

这很有用,但它受到需要在字符串中进行所有变量初始化的限制。

我想了一下,意识到我可以使用 YAML 来序列化/反序列化对象。

以下面的例子为例:

def c
  value = YAML.dump [ { a: "b" } ]
  binding.of_caller(2).eval("var = YAML.load('#{value}')")
end

a()
# => {a: "b"}

这很酷,但是如果我可以完全避免序列化并只编写适当的 do; 那就更好了end; 像这样阻塞:

# doesnt work
def c
  binding.of_caller(2).eval do
    # ideally this would set the variable named "var" in the scope of method "a"
    var = [ { a: "b" } ]
  end
end

如何实现最后一个示例的功能?如果有其他方法,我不需要使用 binding_of_caller

最佳答案

这是我能做的最好的事情,而且我怀疑(尽管我真的很想被证明是错误的),如果你不编写自己的 C 扩展,那么你会发现最好的事情是:

require 'binding_of_caller'

module BindingExtensionEvalBlock
  def eval_block(&block)
    eval("ObjectSpace._id2ref(%d).call(binding)" % block.object_id)
  end
end

class ::Binding
  include BindingExtensionEvalBlock
end

当然,魔法就在这里:

eval("ObjectSpace._id2ref(%d).call(binding)" % block.object_id)

我们获取 Proc 的对象 ID,然后在 Binding#eval 中使用 ObjectSpace#_id2ref从内存中的任何位置检索它并调用它,传入本地绑定(bind)

这是在行动:

def a
  var = 10
  b
  puts var
end

def b
  c
end

def c
  binding.of_caller(2).eval_block do |bnd|
    bnd.local_variable_set(:var, [ { a: "b" } ])
  end
end

a # => {:a=>"b"}

如您所见,我们必须在 block 中执行 bnd.local_variable_set(:var, [ { a: "b"),而不是 var = [ { a: "b"} ] “} ])。 Ruby 中无法更改 block 的绑定(bind),因此我们必须传入绑定(bind) (bnd) 并调用 Binding#local_variable_set .

关于ruby - 使用 block 在父作用域中设置变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37125272/

相关文章:

ruby - 为什么 split (' ' ) 试图变得(太)聪明?

ruby-on-rails - 在 Ruby On Rails 中创建模板并动态替换占位符

ruby-on-rails - 如何在 Rails 3 中创建图表

c++ - 用于位计数的元程序

metaprogramming - JetBrains元编程系统

ruby-on-rails - RubyMine 中的错误 : no ruby interpreter configured for project

ruby-on-rails - 用户回形针重命名上传的文件

c++ - 使用函数地址的模板类的唯一数字 ID

c++ - 具有静态成员变量的奇怪 C++ 模板行为

ruby-on-rails - 如何在 block 中调用 `super`