Ale*_*kin 9 ruby lambda metaprogramming proc
问题的灵感来自于这个问题.
Proc::new
有一个选项可以在方法中没有块调用:
Proc::new
可以仅在具有附加块的方法内调用没有块,在这种情况下,该块被转换为Proc
对象.
当proc
/ lambda
instance作为代码块传递时,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
/ irb
console中测试上面的代码:他们知道有纯粹的执行故障,主要是因为他们修补了procs.
Ruby Issue Tracker 上对此行为进行了一些讨论,请参阅功能 #10499:消除和中的隐式魔法Proc.new
Kernel#proc
。
这是 YARV 的一个实现工件:YARV 将一个块推送到全局 VM 堆栈上,并简单地从堆栈上的最顶层块Proc::new
创建一个。Proc
所以,如果你碰巧打电话Proc.new
,它会很乐意抓取堆栈顶部的任何块,而无需检查它来自哪里。不知何故,在某个地方,在时间的迷雾中,这个(我们称之为)“意外工件”(我实际上宁愿称之为错误)成为了一个记录在案的功能。JRuby(大概还有 Rubinius、Opal、MagLev 等)的开发者宁愿放弃这个功能。
由于大多数其他实现的工作方式完全不同,这种在 YARV 上“免费”出现的行为,使得Proc::new
其他实现上的块和潜在成本更高,并禁止可能的优化(这不会对 YARV 造成伤害,因为 YARV 不会优化)。