为什么从代码块中的双精度参数调用`to_ary`?

saw*_*awa 3 ruby parameters lambda codeblocks

似乎,双精度的块参数调用to_ary了传递的对象,而lambda参数和方法参数则不会发生这种情况。确认如下。

首先,我准备了一个obj在其to_ary上定义方法的对象,该对象返回除数组(即字符串)之外的其他内容。

obj = Object.new
def obj.to_ary; "baz" end
Run Code Online (Sandbox Code Playgroud)

然后,将其传递obj给具有双斜线参数的各种构造:

instance_exec(obj){|**foo|}
# >> TypeError: can't convert Object to Array (Object#to_ary gives String)
Run Code Online (Sandbox Code Playgroud)
->(**foo){}.call(obj)
# >> ArgumentError: wrong number of arguments (given 1, expected 0)
Run Code Online (Sandbox Code Playgroud)
def bar(**foo); end; bar(obj)
# >> ArgumentError: wrong number of arguments (given 1, expected 0)
Run Code Online (Sandbox Code Playgroud)

从上面可以看到,只有代码块尝试obj通过调用(潜在)to_ary方法转换为数组。

为什么代码块的双斜线参数的行为与la​​mbda表达式或方法定义的行为不同?

mrz*_*asa 8

对于您的问题,我没有完整的答案,但我会分享我发现的内容。

简洁版本

可以使用与签名中定义的参数数量不同的参数来调用Procs。如果参数列表与定义不匹配,#to_ary则调用以进行隐式转换。Lambda和方法需要与它们的签名匹配的多个arg。没有执行任何转换,因此#to_ary不被调用。

长版

您所描述的是通过lambda(和方法)和proc(和块)处理参数之间的区别。看一下这个例子:

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)
Run Code Online (Sandbox Code Playgroud)

Proc不需要与定义的数目相同的args,而是#to_ary被调用(您可能知道):

对于使用lambda或创建->()的proc,如果将错误数量的参数传递给proc,则会生成错误。对于使用Proc.new或创建的proc Kernel.proc,静默丢弃多余的参数,并将缺少的参数设置为nil。(文档

此外,Proc调整传递的参数以适合签名:

proc{|head, *tail| print head; print tail}.call([1,2,3])
# >> 1[2, 3]=> nil
Run Code Online (Sandbox Code Playgroud)

资料来源:makandra,因此提出质疑

#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
Run Code Online (Sandbox Code Playgroud)

ruby跟踪器中对其进行了详细描述。

您可以看到将[1,2,3]其拆分为head=1tail=[2,3]。这与多重分配中的行为相同:

head, *tail = [1, 2, 3]
# => [1, 2, 3]
tail
# => [2, 3]
Run Code Online (Sandbox Code Playgroud)

如您#to_ary所见,当proc具有双斜尾关键字args时,也会调用:

proc{|head, **tail| print head; print tail}.call(obj2)
# >> 1{}=> nil
proc{|**tail| print tail}.call(obj2)
# >> {}=> nil
Run Code Online (Sandbox Code Playgroud)

在第一种情况下,[1, 2, 3]return by 的数组obj2.to_ary被拆分为head=1and空尾,因为**tail无法匹配的数组[2, 3]

Lambda和方法没有这种行为。他们需要严格的参数数量。没有隐式转换,因此#to_ary不被调用。

我认为这种区别是在Ruby的以下两行中实现的:

    opt_pc = vm_yield_setup_args(ec, iseq, argc, sp, passed_block_handler,
(is_lambda ? arg_setup_method : arg_setup_block));
Run Code Online (Sandbox Code Playgroud)

并且在这个功能上。我猜#to_ary是在vm_callee_setup_block_arg_arg0_splat中的某个地方被调用了,很可能是在RARRAY_AREF。我很想阅读这段代码的注释,以了解其中发生的情况。