这个问题是受this one启发的。
Proc::new有一个选项,可以在方法内部不加阻塞的情况下进行调用:



proc/lambda实例作为代码块传递时,正在创建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]

可能会看到,对Proc::new的调用都没有发生,也没有调用Proc#initialize和/或Proc#call

问题是: ruby​​是如何在后台创建和执行块包装的?

NB 不要在pry/irb控制台中测试上面的代码:他们知道在纯执行此操作时会出现故障,主要是因为它们修补了proc。

最佳答案

在Ruby Issue Tracker上对此行为进行了一些讨论,请参阅Feature #10499: Eliminate implicit magic in Proc.new and Kernel#proc

这是YARV的实现工件:YARV将块插入到全局VM堆栈中,而Proc::new只是从堆栈中最顶层的块创建Proc。因此,如果您碰巧从用块调用的方法中调用Proc.new,它将很乐意捕获堆栈顶部的任何块,而无需检查它的来源。不知何故,在某个时间的薄雾中,这个“偶然的伪像”(我宁愿称它为错误)(我们称它为“错误”)成为了有据可查的功能。 JRuby的开发人员(大概是Rubinius,Opal,MagLev等)的开发人员宁愿放弃该功能。

由于大多数其他实现的工作方式完全不同,因此此行为在YARV上“免费”出现,这使得块和Proc::new在其他实现上的成本都变得昂贵,并且禁止了可能的优化(这对YARV不利,因为YARV并未进行优化) 。

关于ruby - 传递给 `instance_exec`时如何执行proc,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40739398/

10-16 11:19