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 3.0 以降
Ruby 3.0 以降ではブロック引数で名前付きの引数は使えないので、代わりにパターンマッチを使ったりするしかないかもしれない
[{ a: 1, b: 2 }, { a: 10, b: 20, c: 99 }].map { |h|
h => {a:, b:}
[a, b]
}
#=> [[1, 2], [10, 20]]
でも↑の例なら Hash#values_at
の方が良さそう……
[{ a: 1, b: 2 }, { a: 10, b: 20, c: 99 }].map { |h|
h.values_at(:a, :b)
}
#=> [[1, 2], [10, 20]]
右代入
Ruby 3.0.0 に右代入が登場。これは熱い。
42 => n
n
#=> 42
でも多重代入しようとすると、だめっぽい
42, 42 => a, b
#=> -e:1: syntax error, unexpected ',', expecting end-of-input
#=> 42, 42 => a, b
多重代入したい時には配列を使うとできます
[42, 42] => [a, b] => c
a #=> 42
b #=> 42
c #=> [42,42]
右代入パターンマッチもあります
{ a: 42, b: 4242 } => { a:, b: }
a #=> 42
b #=> 4242
複雑なパターンでもいけます
[{ a: 42, b: 4242 }, { c: 424242, d: 42424242 }] => [{ a:, b: }, { c:, d: }]
a #=> 42
b #=> 4242
c #=> 424242
d #=> 42424242
まとめ
- Ruby の代入の柔軟性の高さ凄い
おまけ
- あ、すごい偶然なんですけど、今日、わしの誕生日です。毎年、みなさまのおかげで生かされております。毎度どうもありがとうございます