为什么在 Ruby 中屈服于 lambda splat 数组参数?
Why does yielding to lambda splat array arguments in Ruby?
在 Ruby 中,使用错误数量的参数调用 lambda
会导致 ArgumentError
:
l = lambda { |a, b| p a: a, b: b }
l.call(1, 2)
# {:a=>1, :b=>2}
l.call(1)
# ArgumentError: wrong number of arguments (given 1, expected 2)
传递数组也不起作用:(因为数组只是一个对象,对吧?)
l.call([3, 4])
# ArgumentError: wrong number of arguments (given 1, expected 2)
除非我使用 splat (*
) 将数组转换为参数列表,但我没有。
但是 ...如果我通过yield
隐式调用lambda,会发生一些意想不到的事情:
def yield_to
yield(1, 2)
yield([3, 4])
end
yield_to(&l)
# {:a=>1, :b=>2}
# {:a=>3, :b=>4} <- array as argument list!?
更令人困惑的是,通过 Method#to_proc
派生的 lambda 确实按预期工作:
def m(a, b)
p a: a, b: b
end
yield_to(&method(:m))
# {:a=>1, :b=>2}
# ArgumentError: wrong number of arguments (given 1, expected 2)
这是怎么回事?
差异可能来自 to_proc
的书写方式:对于简单的 lambda 或 proc,它只是 returns self。对于一个方法,它将 is_from_method
标志设置为 true
,这在过程为 invoked in the VM:
时起作用
if (proc->is_from_method) {
return vm_invoke_bmethod(th, proc, self, argc, argv, passed_block_handler);
}
else {
return vm_invoke_proc(th, proc, self, argc, argv, passed_block_handler);
}
看起来如果过程是从方法创建的,参数将被检查。
显然实施洞穴探险是一种方式。首先,为什么 #call
会为参数不正确的 lambda 抛出异常? Proc::call
explicitly checks for lambdas.
现在让我们追踪 yield
。 It's completely separate from Proc::call
. Starts here, then, then, then (VM_BH_FROM_PROC
just checks that the thing is indeed a proc), then (due to the goto
). I couldn't trace exactly the magic that happens in the second run of the switch, but it only makes sense that it goes through the block_handler_type_iseq
case or exits the switch. Point is, invoke_block_from_c_splattable
知道假定过程的 lambda 状态,但除了验证它确实是 lambda 之外,它不用于任何其他用途。它不会阻止争论。
至于原因部分 - 我什至不确定它是否是故意的,只是缺少 procs 逻辑中的分支。 Lambdas 主要被宣传为 method-like ¯\_(ツ)_/¯
查看文档后,我认为这是 Ruby 中的错误。 Proc class docs 明确表示:
The & argument preserves the tricks if a Proc object is given by &
argument.
据文档说,即使在使用 &
将 lambda 传递给方法时,也应保留处理参数的方式。
以下代码
l = lambda { |a, b| p a: a, b: b }
def yield_to(&block)
yield([3, 4])
p block.lambda?
end
yield_to(&l)
产出
{:a=>3, :b=>4}
true
这是互斥的。
有趣的是,此代码因 ArgumentError
而失败
l = lambda { |a, b| p a: a, b: b }
def yield_to(&block)
block.call([3, 4])
p block.lambda?
end
yield_to(&l)
我在这里回答我自己的问题,因为这是一个已知错误:
https://bugs.ruby-lang.org/issues/12705
它已在 Ruby 2.4.1 中修复(感谢 @ndn)
在 Ruby 中,使用错误数量的参数调用 lambda
会导致 ArgumentError
:
l = lambda { |a, b| p a: a, b: b }
l.call(1, 2)
# {:a=>1, :b=>2}
l.call(1)
# ArgumentError: wrong number of arguments (given 1, expected 2)
传递数组也不起作用:(因为数组只是一个对象,对吧?)
l.call([3, 4])
# ArgumentError: wrong number of arguments (given 1, expected 2)
除非我使用 splat (*
) 将数组转换为参数列表,但我没有。
但是 ...如果我通过yield
隐式调用lambda,会发生一些意想不到的事情:
def yield_to
yield(1, 2)
yield([3, 4])
end
yield_to(&l)
# {:a=>1, :b=>2}
# {:a=>3, :b=>4} <- array as argument list!?
更令人困惑的是,通过 Method#to_proc
派生的 lambda 确实按预期工作:
def m(a, b)
p a: a, b: b
end
yield_to(&method(:m))
# {:a=>1, :b=>2}
# ArgumentError: wrong number of arguments (given 1, expected 2)
这是怎么回事?
差异可能来自 to_proc
的书写方式:对于简单的 lambda 或 proc,它只是 returns self。对于一个方法,它将 is_from_method
标志设置为 true
,这在过程为 invoked in the VM:
if (proc->is_from_method) {
return vm_invoke_bmethod(th, proc, self, argc, argv, passed_block_handler);
}
else {
return vm_invoke_proc(th, proc, self, argc, argv, passed_block_handler);
}
看起来如果过程是从方法创建的,参数将被检查。
显然实施洞穴探险是一种方式。首先,为什么 #call
会为参数不正确的 lambda 抛出异常? Proc::call
explicitly checks for lambdas.
现在让我们追踪 yield
。 It's completely separate from Proc::call
. Starts here, then, then, then (VM_BH_FROM_PROC
just checks that the thing is indeed a proc), then (due to the goto
). I couldn't trace exactly the magic that happens in the second run of the switch, but it only makes sense that it goes through the block_handler_type_iseq
case or exits the switch. Point is, invoke_block_from_c_splattable
知道假定过程的 lambda 状态,但除了验证它确实是 lambda 之外,它不用于任何其他用途。它不会阻止争论。
至于原因部分 - 我什至不确定它是否是故意的,只是缺少 procs 逻辑中的分支。 Lambdas 主要被宣传为 method-like ¯\_(ツ)_/¯
查看文档后,我认为这是 Ruby 中的错误。 Proc class docs 明确表示:
The & argument preserves the tricks if a Proc object is given by & argument.
据文档说,即使在使用 &
将 lambda 传递给方法时,也应保留处理参数的方式。
以下代码
l = lambda { |a, b| p a: a, b: b }
def yield_to(&block)
yield([3, 4])
p block.lambda?
end
yield_to(&l)
产出
{:a=>3, :b=>4}
true
这是互斥的。
有趣的是,此代码因 ArgumentError
l = lambda { |a, b| p a: a, b: b }
def yield_to(&block)
block.call([3, 4])
p block.lambda?
end
yield_to(&l)
我在这里回答我自己的问题,因为这是一个已知错误:
https://bugs.ruby-lang.org/issues/12705
它已在 Ruby 2.4.1 中修复(感谢 @ndn)