如何在传递给`instance_exec`时执行proc

Ale*_*kin 9 ruby lambda metaprogramming proc

问题的灵感来自于这个问题.

Proc::new 有一个选项可以在方法中没有块调用:

Proc::new可以仅在具有附加块的方法内调用没有块,在这种情况下,该块被转换为Proc对象.

proc/ lambdainstance作为代码块传递时,Proc正在创建新实例:

Proc.singleton_class.prepend(Module.new do
  def new(*args, &cb)
    puts "PROC #{[block_given?, cb, *args].inspect}"
    super
  end
end)

Proc.prepend(Module.new do
  def initialize(*args, &cb)
    puts "INIT #{[block_given?, cb, *args].inspect}"
    super
  end
  def call(*args, &cb)
    puts "CALL #{[block_given?, cb, *args].inspect}"
    super
  end
end)

? = ->(*args) { }
[1].each &?
#? [1]
Run Code Online (Sandbox Code Playgroud)

正如人们可能会看到,无论是呼叫Proc::new发生的事情,也没有Proc#initialize和/或Proc#call进行调用.

问题是:ruby如何创建并执行引擎盖下的块包装器?


注意:不要在pry/ irbconsole中测试上面的代码:他们知道有纯粹的执行故障,主要是因为他们修补了procs.

Jör*_*tag 2

Ruby Issue Tracker 上对此行为进行了一些讨论,请参阅功能 #10499:消除中的隐式魔法Proc.newKernel#proc

这是 YARV 的一个实现工件:YARV 将一个块推送到全局 VM 堆栈上,并简单地从堆栈上的最顶层块Proc::new创建一个。Proc所以,如果你碰巧打电话Proc.new,它会很乐意抓取堆栈顶部的任何块,而无需检查它来自哪里。不知何故,在某个地方,在时间的迷雾中,这个(我们称之为)“意外工件”(我实际上宁愿称之为错误)成为了一个记录在案的功能。JRuby(大概还有 Rubinius、Opal、MagLev 等)的开发者宁愿放弃这个功能。

由于大多数其他实现的工作方式完全不同,这种在 YARV 上“免费”出现的行为,使得Proc::new其他实现上的块和潜在成本更高,并禁止可能的优化(这不会对 YARV 造成伤害,因为 YARV 不会优化)。