Ruby块采用数组或多个参数

今天我很惊讶地发现ruby自动找到作为块参数给出的数组的值。

例如:

foo = "foo" bar = "bar" p foo.chars.zip(bar.chars).map { |pair| pair }.first #=> ["f", "b"] p foo.chars.zip(bar.chars).map { |a, b| "#{a},#{b}" }.first #=> "f,b" p foo.chars.zip(bar.chars).map { |a, b,c| "#{a},#{b},#{c}" }.first #=> "f,b," 

我原以为最后两个例子会给出一些错误。

  1. 这是ruby中更一般概念的一个例子吗?
  2. 我不认为我的问题开头的措辞是正确的,我怎么称呼这里发生的事情?

Ruby的块函数对它们有一个怪癖,就是如果你在迭代包含数组的东西,你可以将它们扩展为不同的变量:

 [ %w[ ab ], %w[ cd ] ].each do |a, b| puts 'a=%sb=%s' % [ a, b ] end 

这种模式在使用Hash#each时非常有用,并且你想要突破该对的keyvalue部分: each { |k,v| ... } each { |k,v| ... }在Ruby代码中很常见。

如果你的块有多个参数并且迭代的元素是一个数组,那么它会切换参数的解释方式。 你总是可以强制扩展:

 [ %w[ ab ], %w[ cd ] ].each do |(a, b)| puts 'a=%sb=%s' % [ a, b ] end 

这对于事情更复杂的情况很有用:

 [ %w[ ab ], %w[ cd ] ].each_with_index do |(a, b), i| puts 'a=%sb=%s @ %d' % [ a, b, i ] end 

因为在这种情况下它迭代一个数组另一个被添加的元素,所以每个项目实际上是内部forms为%w[ ab ], 0的元组,如果你的块只接受一个参数,它将被转换为数组。

这与定义变量时可以使用的原理大致相同:

 a, b = %w[ ab ] a # => 'a' b # => 'b' 

这实际上为ab分配了独立的值。 对比:

 a, b = [ %w[ ab ] ] a # => [ 'a', 'b' ] b # => nil 

Ruby块像这样古怪。

规则是这样的,如果一个块接受多个参数并且它产生一个响应to_ary对象,则该对象被扩展。 这使得产生一个数组而不是产生一个元组似乎对于带有两个或更多参数的块的行为方式相同。

yield [a,b]yield a,b确实不同,但是当块只接受一个参数或者块接受可变数量的参数时。

让我certificate这两点

 def yield_tuple yield 1, 2, 3 end yield_tuple { |*a| pa } yield_tuple { |a| p [a] } yield_tuple { |a, b| p [a, b] } yield_tuple { |a, b, c| p [a, b, c] } yield_tuple { |a, b, c, d| p [a, b, c, d] } 

版画

 [1, 2, 3] [1] [1, 2] [1, 2, 3] [1, 2, 3, nil] 

 def yield_array yield [1,2,3] end yield_array { |*a| pa } yield_array { |a| p [a] } yield_array { |a, b| p [a, b] } yield_array { |a, b, c| p [a, b, c] } yield_array { |a, b, c, d| p [a, b, c, d] } 

版画

 [[1, 2, 3]] [[1, 2, 3]] [1, 2] # array expansion makes it look like a tuple [1, 2, 3] # array expansion makes it look like a tuple [1, 2, 3, nil] # array expansion makes it look like a tuple 

最后要表明Ruby中的所有内容都使用duck-typing

 class A def to_ary [1,2,3] end end def yield_arrayish yield A.new end yield_arrayish { |*a| pa } yield_arrayish { |a| p [a] } yield_arrayish { |a, b| p [a, b] } yield_arrayish { |a, b, c| p [a, b, c] } yield_arrayish { |a, b, c, d| p [a, b, c, d] } 

版画

 [#] [#] [1, 2] # array expansion makes it look like a tuple [1, 2, 3] # array expansion makes it look like a tuple [1, 2, 3, nil] # array expansion makes it look like a tuple 

PS,相同的数组扩展行为适用于行为类似于块的proc闭包,而lambda闭包的行为类似于方法。

我原以为最后两个例子会给出一些错误。

事实上,如果从方法传递proc ,它确实会这样工作。 屈服于这样一个proc更加严格 – 它检查它的arity并且不会尝试将数组参数转换为参数列表:

 def m(a, b) "#{a}-#{b}" end ['a', 'b', 'c'].zip([0, 1, 2]).map(&method(:m)) #=> wrong number of arguments (given 1, expected 2) (ArgumentError) 

这是因为zip创建了一个数组(数组), map只生成每个元素,即

 yield ['a', 0] yield ['b', 1] yield ['c', 2] 

另一方面, each_with_index工作:

 ['a', 'b', 'c'].each_with_index.map(&method(:m)) #=> ["a-0", "b-1", "c-2"] 

因为它产生两个单独的值,即元素及其索引,即

 yield 'a', 0 yield 'b', 1 yield 'c', 2