5

After about a year of Ruby, I just saw this somewhere and my mind is blown. Why in the world does this work?

>> words = ['uno', 'dos']
=> ["uno", "dos"]
>> first, second = words
=> ["uno", "dos"]
>> first
=> "uno"
>> second
=> "dos"

Specifically, how does this work:

>> first, second = ['uno', 'dos']

Why can I do this? It makes no syntactical sense!

5
  • 1
    It's surprising that you have never seen this in a year of Ruby. It's exactly what happens when for example iterating over a hash and using two block variables to separate the key and value instead of one two element array (hash.each { |k, v| ... } instead of hash.each { |arr| ... } Commented Oct 24, 2016 at 9:33
  • 1
    You can take this a lot further btw: one, two, three, _, *rest = [*1..10]. Commented Oct 24, 2016 at 9:34
  • @MichaelKohl I had actually been under the impression it was obtained with args[0], and args[1], but this makes much more sense! Thanks! Commented Oct 24, 2016 at 9:35
  • @MichaelKohl: Is _ a placeholder? I've never seen it in this context. I only know it from irb, as equal to the last result. Commented Oct 24, 2016 at 9:46
  • It basically means "I don't care about this". You know how you prefix variable names with _ to denote that you are not intending to use the result without triggering a warning (e.g. _foo)? This is the same, just without even a name. Commented Oct 24, 2016 at 10:17

3 Answers 3

18

It makes no syntactical sense

But this is part of Ruby's syntax! In the Ruby docs it is known as array decomposition:

Like Array decomposition in method arguments you can decompose an Array during assignment using parenthesis:

(a, b) = [1, 2]

p a: a, b: b # prints {:a=>1, :b=>2}

You can decompose an Array as part of a larger multiple assignment:

a, (b, c) = 1, [2, 3]

p a: a, b: b, c: c # prints {:a=>1, :b=>2, :c=>3}

Since each decomposition is considered its own multiple assignment you can use * to gather arguments in the decomposition:

a, (b, *c), *d = 1, [2, 3, 4], 5, 6

p a: a, b: b, c: c, d: d
# prints {:a=>1, :b=>2, :c=>[3, 4], :d=>[5, 6]}

Edit

as Stefan points out in the comments, the docs don't mention that array decomposition also occurs implicitly (i.e. without parenthesis) if there is only one value on the right-hand side:

a, b = [1, 2] works like (a, b) = [1, 2]

Sign up to request clarification or add additional context in comments.

3 Comments

Also, it allows you to swap variables without needing a third one : a,b = b,a
The docs don't mention that array decomposition also occurs implicitly (i.e. without parenthesis) if there is only one value on the right-hand side: a, b = [1, 2] works like (a, b) = [1, 2].
Thanks for the mention! I've updated my example to make use of the term "decomposition" instead and added an example of nested decomposition (been exposed to Clojure at the time of writing that). Also, feel free to amend it if you think something is missing there.
6

Why can I do this? It makes no syntactical sense!

It makes a perfect sense. It is an example of parallel assignment.

When you use = what is happening is each of the list of variables on the left of = are assigned to each of the list of expressions on the right of =.

first, second = ['uno', 'dos']
# is equivalent to
first, second = 'uno', 'dos'

If there are more variables on the left, than expressions on the right, those left variables are assigned with nil:

first, second = 'uno'
first  #=> 'uno'
second #=> nil

As to

words = ['uno', 'dos']
first, second = words
first  #=> 'uno'
second #=> 'dos'

It is not assigning the whole words array to first leaving second with nil, because while parallel assignment Ruby tries to decompose the right side expression, and does so if it is an instance of Array.

[TIL] Moreover, it attempts to call to_ary on the right side expression, and if it responds to the method, decomposes accordingly to that object's to_ary implementation (credits to @Stefan):

string = 'hello world'
def string.to_ary; split end
first, second = string
first  #=> 'hello'
second #=> 'world'

7 Comments

could you elaborate?
What about first, second = words? According to your explanation (more variables on the left, than expressions on the right), this should assign words to first and nil to second.
@Stefan yes, with this regard my answer is misleading and wrong.. I will consider either rephrasing it or deleting, thanks for noticing!
It raises the question why some objects are decomposed during (or before) parallel assignment and other's aren't.
Additionally, it attempts to calls to_ary. For example, s = 'hello world'; def s.to_ary; split; end would allow to assign words via a, b = s
|
0

This is called multiple assignment, handy to assign multiple variables at once. example

one, two = 1,2
puts one #=>1
puts two #=>2
one, two = [1,2] # this makes sense
one, two = 1  # obviously this doesn't it will assign nil to two

Hope its bit clear now

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.