Ruby

[Ruby]ブロック引数への代入方法

More than 1 year has passed since last update.

2016/09/21 2.4.0で入ったif文の条件式での多重代入について追記


ブロック引数への代入方法

Rubyでは配列をグルグル回すときにメソッド+ブロックをつけて色々処理することが多いですが、そのブロック引数の扱いで

結構しられていない(?)多重代入のルールを知ってると簡単に書けたりすることがあります。

こういうパターンはよく見ます。

ary = [

[1, 2],
[3, 4]
]
ary.each do |a, b|
p [a, b]
end

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

eachでブロックに渡されるのは、例えば最初の要素なら[1, 2]という配列だったりするのですが、それがa, bに分解されてブロック引数にセットされるパターンですね。

でも、なぜか

ary = [

[[1, 2], :a],
[[3, 4], :b]
]

というようなパターンだと、こういうコードをよく見かけます。

ary.each do |e, s|

p [e[0], e[1], s]
end
#=>
#[1, 2, :a]
#[3, 4, :b]

e[1, 2]が代入されて、s:aが代入されるケースですね。

でも、これはブロック引数に括弧をつけると、こう書けます

ary.each do |(a, b), c|

p [a, b, c]
end
#=>
#[1, 2, :a]
#[3, 4, :b]

これは配列がもっとネストしてても大丈夫で、例えば

ary = [

[[1, [2, [3, 4]]], :a],
[[5, [6, [7, 8]]], :b]
]

という配列があったとしたら、

ary.each do |(a, (b, (c, d))), e|

p [a, b, c, d, e]
end
#=>
#[1, 2, 3, 4, :a]
#[5, 6, 7, 8, :b]

と、括弧をネストするとそれぞれ分解して代入してくれます。

lispのdestructuring-bindみたいな感じですね。

で、よくあるパターンだと、例えば各人の教科別の点数を配列で持っていて、その各要素が

[名前, [教科, 点数]]

というような持ち方で、

ary = [

[:taro, [:math, 10]],
[:taro, [:japanese, 20]],
[:jiro, [:math, 30]],
[:jiro, [:japanese, 40]],
[:saburo, [:math, 50]]
]

ってなってるようなケースで、教科関係なくすべてのテストの合計点を求めたい時に、

injectでグルグルまわすとき、

total = ary.inject(0){|acc, (_, (_, score))|

acc + score
}
puts total #=> 150

というように、injectのアキュームレータ(acc)に続けて、配列の構造に合わせて括弧をネストさせることで[]を使わず欲しい値だけが取得できたりします。(_は変数としては要るけど処理としては使わない時に「使わないよ」という意味の時に使います)


多重代入

ブロック引数への代入方法は多重代入の方法と同じそうです。

なので、多重代入でもこういったネストの構造を代入することができます。

name, (subject, score) = [:taro, [:math, 100]]

p [name, subject, score] #=> [:taro, :math, 100]


2016/09/21追記

2.4.0preview2 が出ましたね!

ブロック引数ではないですが、多重代入に関して記載していたので、追記です。

2.4.0からif文の条件式部分でも多重代入が出来るようになったそうで試してみました。

def m(flag)

if flag
[1, [2, 3], 4]
end
end

# nilが返された場合
if (a, (b, c), d = m(false))
p [a, b, c, d]
else
p :result_is_nil
end
# => result_is_nil

# ネストした配列を返す関数から値を受け取る
if (a, (b, c), d = m(true))
p [a, b, c, d]
else
p :result_is_nil
end
# => [1, 2, 3, 4]

p [a, b, c, d]
# => [1, 2, 3, 4]

条件式自体は括弧で括る必要があるみたいですね。

で一番下の p [a, b, c, d] の結果から,(rubyならまぁ当然といえば当然かもしれませんが) if文が終わった後もそのスコープは続いてるんですね。