はじめに
"hoge".tap { |str| str.reverse }
# => "hoge"
"hoge".tap { |str| break str.reverse }
# => "egoh"
Object#tap
に渡すブロックの中で break
すると、その引数を返すことができる。
このテクニックの活用方法はこれまでもいくつか研究されてきた。
自分もつい先日、コーディング中に tap break を使うタイミングを見つけたので、そのことについて書いていく。
やりたいこと
table = Hash.new
ary = ["aa", "aaa", "abcd", "bbbb", "ccc"]
ary.each do |val|
key = val.size
if table.has_key? key
table[key] << val
else
table[key] = [val]
end
end
table
# => {2=>["aa"], 3=>["aaa", "ccc"], 4=>["abcd", "bbbb"]}
キーに対応する値が配列となるハッシュがあり、入力されるデータを分類しハッシュに格納していく、という場面。
ここでは例として、入力されるデータは文字列、分類基準は文字列の長さとした。
素直に if を用いて書いたが、tap break を使えばもっと短くできるのではないかと思った。
group_by
使えば?
ary.group_by(&:size)
# => {2=>["aa"], 3=>["aaa", "ccc"], 4=>["abcd", "bbbb"]}
やりたいことはまさに group_by
なのだが、いくつかの理由で今回は使用しない。
ひとつは ary
は別々に順次入力されるという状況だったので、 group_by
の結果を毎回マージするのが面倒くさいから。
もうひとつは、先ほどのサンプルコードでは省略しているが、追加する val
に加工をする必要があったから。
tap break 最終案
ary.each do |val|
key = val.size
table[key].tap { |t| break table[key] = [] unless t } << val
end
table
# => {2=>["aa"], 3=>["aaa", "ccc"], 4=>["abcd", "bbbb"]}
-
table[key]
がnil
である場合- ブロックの中身が戻り値となるので、
-
(table[key] = []) << val
となる
-
table[key]
がnil
でない場合- レシーバが戻り値となるので、
-
table[key] << val
となる
Ruby2.5で tap
の親戚みたいなメソッド yield_self
が追加されたが、 ブロックの中身を選択的に捨てたり返したりできる tap
楽しいよね、という。
tap break じゃなくてもいいのでは?
まあ、はい。
or演算子のやつはスッキリしてて個人的にいいと思った。
table[key]
が nil
のときだけ右辺が評価されるので。
ary = ["aa", "aaa", "abcd", "bbbb", "ccc"]
# 条件演算子、三項演算子 を用いる場合
table = Hash.new
ary.each do |val|
key = val.size
(table[key].nil? ? table[key] = [] : table[key]) << val
end
table
# => {2=>["aa"], 3=>["aaa", "ccc"], 4=>["abcd", "bbbb"]}
# or演算子を用いる場合
table = Hash.new
ary.each do |val|
key = val.size
(table[key] || table[key] = []) << val
end
table
# => {2=>["aa"], 3=>["aaa", "ccc"], 4=>["abcd", "bbbb"]}
おわりに
読みづらいので他の人が読むコードでは書かない方がいいと思った。