Search code examples
rubyblockenumerable

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.

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?

Solution

  • 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:

    [ %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| ... } is very common in Ruby code.

    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[ a b ], 0 internally, which will be converted to an array if your block only accepts one argument.

    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. Contrast with:

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