2016-11-22 12 views
9

这个问题受this one的启发。当传递到`instance_exec`时如何执行proc

Proc::new有一个选项,而不在一个方法中的块被称为:

Proc::new可以在没有一个块只在具有附接的块的方法被调用,在这种情况下该块被转换为Proc目的。

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控制台测试上面的代码:他们知道有毛刺用这种纯粹的执行,主要是因为它们的补丁特效。

+0

版本的Ruby? – fl00r

+0

@ fl00r MRI 2.1-2.3,我真的相信所有版本在这里都是一样的。 – mudasobwa

+0

我不能接收你的输出'ruby 2.2.2p95(2015-04-13 revision 50295)[x86_64-darwin14]'我得到了'[1]。每个&λ; => [1]' – fl00r

回答

2

在Ruby问题跟踪器上已经对此行为进行了一些讨论,请参阅Feature #10499: Eliminate implicit magic in Proc.new and Kernel#proc

这是YARV的一个实现工件:YARV在全局VM堆栈上推动块,并且Proc::new只是从堆栈中最顶端的块创建一个Proc。所以,如果你碰巧在一个被块调用的方法中调用Proc.new,它会很高兴地抓住堆栈顶部的任何块,而不会检查它来自哪里。不知何故,在某种程度上,这个(我们称之为)“偶然的神器”(我实际上更愿意称它为一个bug)变成了一个记录的特征。 JRuby(大概Rubinius,Opal,MagLev等)的开发人员宁愿摆脱它​​的一个特征。由于大多数其他实现的工作方式完全不同,这种在YARV上“免费”的行为使得在其他实现中块和在成本上更昂贵,并禁止可能的优化(这不会损害YARV,因为YARV不会没有优化)。

+0

“YARV将一个指针指向VM堆栈上的块” - 这看起来像是我问题的答案的一半。这是否意味着1)任何其他ruby实现都为'[1] .each&λ'调用'Proc :: new',并且2)开发人员无法干涉YARV中的[1] .each&λ',因为存在没有'Proc'实例创建?AFAIU,堆栈是否恰好包含一个块作为“先出来,” YARV会愉快地执行它绕过任何其他Ruby代码/包装/什么的,对不对? – mudasobwa

+0

不确定。我的回答是基于Charles Nutter的一条单线帖子的模糊记忆,我现在在我的答案中找到并链接了这条记录。然而,它并不解释如何JRuby做到了。 –

+0

明白了,谢谢。如果您的答案更新为“无论采用哪种解决方案来干预传递给方法的块,它都不是跨VM实现”,我将不胜感激。 – mudasobwa