我没有你的问题的完整答案,但我会分享我的发现。
短版
Procs 允许使用与签名中定义的参数数量不同的参数来调用。如果参数列表与定义不匹配,则调用#to_ary 进行隐式转换。 Lambda 和方法需要与其签名匹配的参数数量。不执行任何转换,这就是不调用 #to_ary 的原因。
加长版
您所描述的是通过 lambdas(和方法)和 procs(和块)处理参数之间的区别。看看这个例子:
obj = Object.new
def obj.to_ary; "baz" end
lambda{|**foo| print foo}.call(obj)
# >> ArgumentError: wrong number of arguments (given 1, expected 0)
proc{|**foo| print foo}.call(obj)
# >> TypeError: can't convert Object to Array (Object#to_ary gives String)
Proc 不需要与它定义的相同数量的 args,并且调用了 #to_ary(您可能知道):
对于使用lambda 或->() 创建的proc,如果传递给proc 的参数数量错误,则会产生错误。对于使用Proc.new 或Kernel.proc 创建的过程,额外的参数会被静默丢弃,缺失的参数会设置为nil。 (Docs)
更重要的是,Proc 调整传递的参数以适应签名:
proc{|head, *tail| print head; print tail}.call([1,2,3])
# >> 1[2, 3]=> nil
来源:makandra、SO question。
#to_ary 用于此调整(这是合理的,因为#to_ary 用于隐式转换):
obj2 = Class.new{def to_ary; [1,2,3]; end}.new
proc{|head, *tail| print head; print tail}.call(obj2)
# >> 1[2, 3]=> nil
在a ruby tracker中有详细描述。
您可以看到[1,2,3] 被拆分为head=1 和tail=[2,3]。这与多分配中的行为相同:
head, *tail = [1, 2, 3]
# => [1, 2, 3]
tail
# => [2, 3]
正如您所注意到的,当 proc 具有双重关键字 args 时,也会调用 #to_ary:
proc{|head, **tail| print head; print tail}.call(obj2)
# >> 1{}=> nil
proc{|**tail| print tail}.call(obj2)
# >> {}=> nil
在第一种情况下,obj2.to_ary 返回的[1, 2, 3] 数组被拆分为head=1 和空尾,因为**tail 无法匹配[2, 3] 的数组。
Lambda 和方法没有这种行为。它们需要严格数量的参数。没有隐式转换,所以不会调用#to_ary。
我认为这个区别是在 Ruby 源码的these two lines 中实现的:
opt_pc = vm_yield_setup_args(ec, iseq, argc, sp, passed_block_handler,
(is_lambda ? arg_setup_method : arg_setup_block));
在this function。我猜#to_ary 在vm_callee_setup_block_arg_arg0_splat 的某个地方被调用,很可能是在RARRAY_AREF。我很想阅读此代码的注释以了解其中发生的情况。