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

  • 37
    いいね
  • 3
    コメント

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 の代入の柔軟性の高さ凄い

おまけ

この投稿は Ruby Advent Calendar 201618日目の記事です。