9

Given

a = [[:a, :b, :c]]

1) I understand this

a.each{|(x, y), z| p z} # => :b

that there are two variables (x, y) and z, so the third element :c is thrown away, and z matches :b. And I understand this

a.each{|(x, y), z| p y} # => nil

that (x, y) matches :a, and since it is not an array, there are no elements to it, and so y matches nil.

But how does

a.each{|(x, y), z| p x} # => :a

work? I expect nil to be returned.

2) Why are the return values like this?

a.each{|(x, y)| p x} #=> :a
a.each{|(x, y)| p y} #=> :b

I expect them to both return nil.

sawa
  • 165,429
  • 45
  • 277
  • 381

1 Answers1

14

It's because of the syntax of parallel assignment.

a = [[:a, :b, :c]]

So a.each has only one element to iterate, which is [:a, :b, :c].

In the first case:

(x, y), z = [:a, :b, :c]
#=> x == :a, y == nil, z == :b

Here (x, y) is an array to match the first element :a, and x gets it, then z simply matches the second element :b.

And in the second case:

(x, y) = [:a, :b, :c]
#=> x == :a, y == :b

Here (x, y) as an entire array matches the array [:a, :b, :c], so x and y get :a and :b respectively.

This is just like requiring the "args + optional args (keyword args) + rest args" combination match provided arguments. It is just smart enough to take arguments by sequence.

Another smart example:

(a,b) = 1,2
=> [1, 2] # array match
#=> a == 1, b == 2

(a,b)=[1,2]
=> [1, 2] # array match
#=> a == 1, b == 2

In either case above, it will simply make the best guess on what it should take.

sawa
  • 165,429
  • 45
  • 277
  • 381
Jing Li
  • 14,547
  • 7
  • 57
  • 69
  • Okay, so in the first case, the smart assignment takes place twice, and in the second part of it, (x, y) = :a results in x = :a and y = nil. I got it. – sawa Aug 23 '12 at 16:09
  • Nice. I use this all the time without realizing it was connected with parallel assignment. I also found a blog that calls it "destructuring". – Kelvin Mar 22 '13 at 21:18