a_proc = Proc.new {|a,b,*c| p c; c.collect {|i| i*b }}
puts a_proc[2,2,4,3]
上面的代码根据https://ruby-doc.org/core-2.2.0/Proc.html非常直观, a_proc [2,2,4,3] 只是 a_proc.call(2,2, 4,3)隐藏“呼叫”
但以下(效果很好)让我很困惑
a=[2,2,4,3]
puts a_proc.call(a)
puts a_proc.call(*a)
它似乎与普通函数调用非常不同,因为它不会检查传入的数字参数。
但是,正如预期的那样,如果同样使用参数
,则调用语义的方法将引发错误def foo(a,b,*c)
c.collect{|i| i*b}
end
foo([1,2,3,4]) #`block in <main>': wrong number of arguments (given 1, expected 2+) (ArgumentError)
foo(*[1,2,3,4]) #works as expected
我认为这种不一致性不是设计故障,所以对此的任何见解都将受到赞赏。
答案 0 :(得分:4)
块使用与用于将参数绑定到参数的方法不同的语义。
在这方面,块语义更类似于赋值语义而不是方法语义。事实上,在旧版本的Ruby中,块字面上使用了参数绑定的赋值,你可以这样写:
class Foo; def bar=(val) puts 'setter called!' end end
some_proc = Proc.new {|$foo, @foo, foo.bar|}
some_proc.call(1, 2, 3)
# setter called!
$foo #=> 1
@foo #=> 2
值得庆幸的是,自Ruby 1.9以来不再是这种情况。但是,保留了一些语义:
to_ary
消息(如果它不是Array
已经存在)并且参数将被绑定到Array
nil
注意:#1是使Hash#each
如此精美工作的原因,否则,您总是必须解构传递给块的数组。
简而言之,块参数的绑定方式与多次赋值的绑定方式大致相同。您可以想象没有setter,indexers,globals,实例变量和类变量的赋值,只有局部变量,这就是块的参数绑定的工作原理:复制并粘贴块中的参数列表,复制并粘贴参数列表从yield
开始,在=
之间插入一个Proc
标志,然后您就明白了。
现在,你实际上并没有谈论一个街区,但是,你在谈论一个Proc
。为此,您需要知道一些重要的事情:两种种类Proc
,不幸的是,它们是使用同一个类实现的。 (IMO,它们应该是两个不同的类。)一种称为 lambda ,另一种通常称为 proc (令人困惑,因为两者都是{{1 }}为s)。
Procs就像块一样,既涉及参数绑定和参数传递(即上述赋值语义),也涉及return
的行为(它从最接近的词法封闭方法返回) )。
Lambdas的行为类似于方法,既涉及参数绑定和参数传递(即严格的参数检查),也涉及return
的行为(它从lambda本身。)
一个简单的助记符:&#34;阻止&#34;和&#34; proc&#34;韵,&#34;方法&#34;和&#34; lambda&#34;都是希腊人。
对你的问题的一点评论:
a_proc [2,2,4,3] 只是 a_proc.call(2,2,4,3)隐藏“通话”的语法糖
这是不语法糖。相反,Proc
只是将[]
方法定义为与call
完全相同。
是什么语法糖是这样的:
a_proc.(2, 2, 4, 3)
每次出现
foo.(bar, baz)
被解释为
foo.call(bar, baz)
答案 1 :(得分:2)
我相信可能令你困惑的是Procs的一些属性。如果给它们一个数组参数,它们将自动splat它。此外,ruby块通常有一些处理块参数的有趣方法。您期望的行为是您将使用Lambda获得的行为。我建议您阅读Proc.lambda? documentation和be careful when calling a ruby block with an array。
现在,让我们从splat运算符开始,然后转到ruby处理块参数的方式:
def foo(a, b, *c)
c.map { |i| i * b } # Prefer to use map alias over collect
end
foo([1, 2, 3, 4]) # `block in <main>': wrong number of arguments (given 1, expected 2+) (ArgumentError)
foo(*[1, 2, 3, 4]) # works as expected
因此,在您的参数错误中,它是有道理的:def foo()
至少需要两个参数:a
,b
,以及*c
的多个参数。 *
为the splat operator。它会将一个数组转换为单独的参数,或者在相反的情况下,将一个可变数量的参数转换为数组。因此,当您说foo([1,2,3,4])
时,您正在给foo
一个参数a
,它是[1,2,3,4]
。您没有设置b
或*c
。例如,因为您要设置foo(1, 1, 1, 2, 3, 4])
,a
和b
,所以可以使用c
。这将是同一件事:foo(1, 1, *[1,2,3,4])
。
现在foo(*[1, 2, 3, 4])
按预期工作,因为splat运算符(*
)将其转换为foo(1, 2, 3, 4)
或等效foo(1, 2, *[3, 4])
好的,现在我们已经覆盖了splat运算符,让我们回顾一下下面的代码(我做了一些小改动):
a_proc = Proc.new { |a, b, *c| c.map { |i| i * b }}
a = [1, 2, 3, 4]
puts a_proc.call(a)
puts a_proc.call(*a)
请记住,如果块/ proc被赋予一个array
参数,它们将自动splat
它。因此,如果您有一组数组arrays = [[1, 1], [2, 2], [3, 3]]
并且arrays.each { |a, b| puts "#{a}:#{b}" }
,那么您将获得1:1
,2:2
和3:3
作为输出。当每个元素作为参数传递给块时,它会看到它是一个数组并对其进行splats,将元素分配给尽可能多的给定块变量。您可以获得a
,而不只是将该数组放在a = [1, 1]; b = nil
a = 1; b = 1
中。它与proc做同样的事情。
a_proc.call([1, 2, 3, 4])
已转为Proc.new { |1, 2, [3, 4]| c.map { |i| i * b }}
,并将输出[6, 8]
。它会自动拆分参数。