Ruby
RubyDay 18

Rubyの多重代入あれこれまとめ

More than 1 year has passed since last update.

Ruby の多重代入はいろいろな書き方があって便利ですが、忘れがちなのでまとめておきます


変数への代入


何の変哲も無い代入

a = 1 #=> 1

a #=> 1


右辺が , 区切りで複数ある場合には配列に変換される

a = 1, 2 #=> [1, 2]

a #=> [1, 2]


左辺も , 区切りで複数ある場合には配列に変換された上で、左辺の各変数に右辺の各要素が代入される

a, b = 1, 2 #=> [1, 2]

a #=> 1
b #=> 2


右辺の要素の数が左辺の変数の数に満たない場合には、右辺の残りの要素に nil が代入される

a, b = 1 #=> 1

a #=> 1
b #=> nil


右辺の要素の数の方が多い場合、余った要素は無視される

a, b = 1, 2, 3 #=> [1, 2, 3]

a #=> 1
b #=> 2


右辺の要素の残り全部を配列として受け取ることもできる


  • 変数に * をつけると、残り全部を配列として受け取れる

a, *b = 1, 2, 3 #=> [1, 2, 3]

a #=> 1
b #=> [2, 3]


  • 残りの要素数が 1 つでも配列になる

a, *b = 1, 2 #=> [1, 2]

a #=> 1
b #=> [2]


  • 残りの要素がない場合には nil ではなく空の配列 [] になる

a, *b = 1 #=> 1

a #=> 1
b #=> []


先頭だけ取って、あとは捨てる


  • 明示的に捨てる場合

a, * = 1, 2, 3 #=> [1, 2, 3]

a #=> 1



  • * を省略して , で終わっても、以降は捨てられる

a, = 1, 2, 3 #=> [1, 2, 3]

a #=> 1


末尾だけ取って、後は捨てる

*, b = 1, 2, 3 #=> [1, 2, 3]

b #=> 3


先頭と末尾だけ取って、後は捨てる

a, *, b = 1, 2, 3, 4 #=> [1, 2, 3, 4]

a #=> 1
b #=> 4


2個目と3個目だけ取って、後は捨てる

_, a, b, * = 1, 2, 3, 4 #=> [1, 2, 3, 4]

a #=> 2
b #=> 3



  • _ は普通の変数なので、実は捨てられていなくて、アクセスできる


    • pry などでは特殊な動きをするので注意




全部捨てる

* = 1, 2, 3 #=> [1, 2, 3]

えっ、全部捨てるなら、なんで代入にした?


ネスト


  • 要素が配列になっている場合、変数側に () を使うと、1レベル下の要素を代入することができる

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

a #=> 1
b #=> 2
c #=> 3


Hash


  • Hash は通常では1つの値として代入される

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

a #=> {:a=>1, :b=>2}
a, b = { a: 1, b: 2 } #=> {:a=>1, :b=>2}
a #=> {:a=>1, :b=>2}
b #=> nil



  • * をつけると Hash#to_a されて多重代入できるようになる

a = *{ a: 1, b: 2 } #=> [[:a, 1], [:b, 2]]

a #=> [[:a, 1], [:b, 2]]
a, b = *{ a: 1, b: 2 } #=> [[:a, 1], [:b, 2]]
a #=> [:a, 1]
b #=> [:b, 2]


ブロック引数への代入

多重代入の基礎が分かったら、ブロック引数への代入に応用してみましょう。


なんの変哲も無い代入

[1, 2, 3].map { |n| n } #=> [1, 2, 3]


配列をそのまま受け取る


  • この例では * は省略できる

[[1, 2], [3, 4]].map { |*a| a } #=> [[1, 2], [3, 4]]


先頭だけをいただく

[[1, 2], [3, 4]].map { |a,| a } #=> [1, 3]


足りなければ nil になる

[[1, 2], [3, 4]].map { |a, b, c| c } #=> [nil, nil]


ブロックの代わりに lambda を使う場合、引数の数に要素の数が満たないと死ぬ

[[1, 2], [3, 4]].map(&->(a, b, c) { c })

#=> ArgumentError: wrong number of arguments (given 1, expected 3)


  • ので、要素の数が足りない恐れがあるときには、さらに () で括ると「第一引数にArray を受け取って、ネストした代入をする」みたいな扱いになって通る

[[1, 2], [3, 4]].map(&->((a, b, c)) { [b, c] }) #=> [[2, nil], [4, nil]]


名前付き引数


  • 通常は Hash が渡ってきても多重代入にはならない

[{ a: 1, b: 2 }, { a: 10, b: 20 }].map { |a, b| [a, b] }

#=> [[{:a=>1, :b=>2}, nil], [{:a=>10, :b=>20}, nil]]



  • Hash#to_a を挟めば、それっぽくはなる

[{ a: 1, b: 2 }, { a: 10, b: 20 }].map(&:to_a).map { |a, b| [a, b] }

#=> [[[:a, 1], [:b, 2]], [[:a, 10], [:b, 20]]]


  • 名前付き引数を使うと、value だけを代入することができる

[{ a: 1, b: 2 }, { a: 10, b: 20 }].map { |a:, b:| [a, b] }

#=> [[1, 2], [10, 20]]


  • ただし、名前付き引数を使うときには、想定されない key が来ると死ぬ

[{ a: 1, b: 2 }, { a: 10, b: 20, c: 99 }].map { |a:, b:| [a, b] }

#=> ArgumentError: unknown keyword: c


  • でも、最後に **x みたいなのを書けば、名前付きで指定していない部分の Hash がもらえる

[{ a: 1, b: 2 }, { a: 10, b: 20, c: 99 }].map { |a:, b:, **x| [a, b, x] }

#=> [[1, 2, {}], [10, 20, {:c=>99}]]


  • 捨てたい時は変数名を省略して ** だけにする

[{ a: 1, b: 2 }, { a: 10, b: 20, c: 99 }].map { |a:, b:, **| [a, b] }

#=> [[1, 2], [10, 20]]


  • 配列の最後の要素が Hash のときにもキーワード引数が使える

[[1, 2, 3, { a: 1, b: 2 }], [4, 5, 6, { a: 10, b: 20 }]].map { |*ary, a:, b:| [a, b] }

#=> [[1, 2], [10, 20]]


  • ネストの先で名前付き引数を使うことはできない

[[{ b: 10 }, 1]].map { |h, n| h } #=> [{:b=>10}]

[[{ b: 10 }, 1]].map { |(b:), n| b } #=> SyntaxError: unexpected '}', expecting end-of-input


まとめ


  • Ruby の代入の柔軟性の高さ凄い


おまけ