简体   繁体   English

Ruby块采用数组或多个参数

[英]Ruby block taking array or multiple parameters

Today I was surprised to find ruby automatically find the values of an array given as a block parameter. 今天,我很惊讶地发现ruby自动找到作为块参数给出的数组的值。

For example: 例如:

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,"

I would have expected the last two examples to give some sort of error. 我本以为最后两个示例会出现某种错误。

  1. Is this an example of a more general concept in ruby? 这是红宝石中更一般概念的示例吗?
  2. I don't think my wording at the start of my question is correct, what do I call what is happening here? 我不认为我在问题开头的措辞是正确的,我怎么称呼这里发生的事情?

Ruby block are quirky like that. 像这样的Ruby块是古怪的。

The rule is like this, if a block takes more than one argument and it is yielded a single object that responds to to_ary then that object is expanded. 规则是这样的,如果一个块接受多个参数,并且产生一个响应to_ary对象,则该对象将被扩展。 This makes yielding an array versus yielding a tuple seem to behave the same way for blocks that take two or more arguments. 对于带有两个或多个参数的块,这使得产生数组与产生元组的行为似乎相同。

yield [a,b] versus yield a,b do differ though when the block takes one argument only or when the block takes a variable number of arguments. yield [a,b]yield a,b的确有所不同,尽管当该块仅使用一个参数或当该块使用可变数量的参数时。

Let me demonstrate both of that 让我展示这两个

def yield_tuple
  yield 1, 2, 3
end

yield_tuple { |*a| p a }
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] } 

prints 版画

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

Whereas

def yield_array
  yield [1,2,3]
end

yield_array { |*a| p a }
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] }

prints 版画

[[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

And finally to show that everything in Ruby uses duck-typing 最后证明Ruby中的所有内容都使用鸭式输入法

class A
  def to_ary
    [1,2,3]
  end
end

def yield_arrayish
  yield A.new
end

yield_arrayish { |*a| p a }
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] }

prints 版画

[#<A:0x007fc3c2969190>]
[#<A:0x007fc3c2969050>]
[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, the same array expansion behavior applies for proc closures which behave like blocks, whereas lambda closures behave like methods. PS,相同的数组扩展行为适用于proc闭包,其行为类似于块,而lambda闭包的行为类似于方法。

Ruby's block mechanics have a quirk to them, that is if you're iterating over something that contains arrays you can expand them out into different variables: Ruby的块机制对其有一个怪癖,也就是说,如果要遍历包含数组的内容,则可以将它们扩展为不同的变量:

[ %w[ a b ], %w[ c d ] ].each do |a, b|
  puts 'a=%s b=%s' % [ a, b ]
end

This pattern is very useful when using Hash#each and you want to break out the key and value parts of the pair: each { |k,v| ... } 当使用Hash#each并想分解成对的keyvalue部分时,此模式非常有用: each { |k,v| ... } each { |k,v| ... } is very common in Ruby code. each { |k,v| ... }在Ruby代码中非常常见。

If your block takes more than one argument and the element being iterated is an array then it switches how the arguments are interpreted. 如果您的块接受多个参数, 并且要迭代的元素是一个数组,则它将切换如何解释参数。 You can always force-expand: 您可以随时强制展开:

[ %w[ a b ], %w[ c d ] ].each do |(a, b)|
  puts 'a=%s b=%s' % [ a, b ]
end

That's useful for cases where things are more complex: 这对于情况更复杂的情况很有用:

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

Since in this case it's iterating over an array and another element that's tacked on, so each item is actually a tuple of the form %w[ ab ], 0 internally, which will be converted to an array if your block only accepts one argument. 由于在这种情况下,它遍历数组附加的另一个元素,因此每一项实际上都是%w[ ab ], 0形式的元组%w[ ab ], 0内部为%w[ ab ], 0 ,如果您的块仅接受一个参数,则它将转换为数组。

This is much the same principle you can use when defining variables: 这与定义变量时可以使用的原理大致相同:

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

That actually assigns independent values to a and b . 这实际上为ab分配了独立的值。 Contrast with: 与:

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

I would have expected the last two examples to give some sort of error. 我本以为最后两个示例会出现某种错误。

It does in fact work that way if you pass a proc from a method. 实际上,如果您从方法中传递proc ,它确实会以这种方式工作。 Yielding to such a proc is much stricter – it checks its arity and doesn't attempt to convert an array argument to an argument list: 产生这样的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)

This is because zip creates an array (of arrays) and map just yields each element, ie 这是因为zip创建(数组)数组,而map仅产生每个元素,即

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

each_with_index on the other hand works: 另一方面, each_with_index起作用:

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

because it yields two separate values, the element and its index, ie 因为它会产生两个单独的值,即元素及其索引,即

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM