普段は使うことはないけど、「tap」というメソッド名は耳にしたことがある人は少なくないでしょう。
元々はメソッドチェーンの途中経過を見るためのデバッグ目的で作られたAPIですが、それ以外の用途で活用もできる、おもしろいメソッドです。この記事では Rubyの「tap」メソッドの使い方について掘り下げていきたいと思います。
tapメソッドとは?
冒頭でも述べましたが、「tap」はメソッドチェーンの中に入り込んでデバッグ等をするためのメソッドとして主に使用します。
Rubyの「tap」メソッドは、公式ドキュメントでは「self を引数としてブロックを評価し、self を返します。」と概要で紹介されています。
【公式リファレンス】instance method Object#tap
これだけ読んだだけでは、なかなか理解が難しいメソッドですね。実際にサンプルコードを見ながら「tap」メソッドの使い方を確認しましょう。
tapの基本
次のサンプルコードは、配列を降順で並び替えるコードです。(注:まだtapは使っていません)
p [40, 30, 10, 30, 50]
.sort
.reverse
#=> [50, 40, 30, 30, 10]
次に「sort」と「reverse」メソッドの後に「tap」メソッド挟んで、配列が昇順でソートされたタイミングと、配列を反転して降順でソートしたタイミングで内容を画面に出力します。
[40, 30, 10, 30, 50]
.sort
.tap{|array| p array}
.reverse
.tap{|array| p array}
実行結果
-------------------
[10, 30, 30, 40, 50]
[50, 40, 30, 30, 10]
上のコードの呼び出し階層をイメージにすると、次のようになります。
「tap」メソッドのブロックには、「tap」メソッドを呼び出したレシーバー自身が引数に渡されるため、ここでは昇順でソートされた配列がブロックの引数に渡されます。
そして「tap」メソッドは、レシーバーをそのままメソッドの結果として返すため、昇順でソートされた配列が、そのまま次の「reverse」メソッドに受け渡され、それ以降の「tap」メソッドも同様の動きになります。
このように「tap」メソッドは、指定したブロックを実行し、処理結果に関係なくレシーバ自身を返すため、メソッド全体の結果に影響を及ぼすことなく、メソッドチェーンの途中で変数の値の確認するなどのデバッグで利用されます。
これで Rubyの「tab」メソッドの処理内容が少し分かって頂けたのではないでしょうか。
tapとbreak使ったテクニック
「tap」メソッドは、元々はメソッドチェーンの途中の過程をデバッグするために作られたAPIですが、「tap」と「break」を組み合わせて使うことで、ブロックの評価結果を「tap」の戻り値と返すことができ、おもしろい処理を組むことができます。
まずは簡単な例で「break」を使ってブロックの処理を抜けると、どんな結果になるのか確認してみます。
value = 10
p value.tap{|x| break x * 2 }
実行結果
-------------------
20
このように、通常「tap」はレシーバー(呼び出し元のオブジェクト)を戻り値に返しますが、「break」でブロックを抜けると、ブロックの評価結果が「tap」メソッドの戻り値として返されます。
これを利用して、次のような処理を書けます。
変数に値を代入しつつ値を取得
ハッシュへの値の代入と、代入した値の取得を同時に行います。
次の例では、ハッシュ型の変数「hash」をレシーバーに「tap」メソッドのブロックで値を代入します。そして、ブロックの評価結果にはハッシュに格納した値が評価結果として返されます。
hash = {}
value = hash.tap{ |h| break h[:key] = 55 }
p value
実行結果
-------------------
55
HTTPのアクセスをパイプラインで実装
パイプラインで次々と「tap」メソッドの処理結果を、次の「tap」メソッドに渡していくことで、RubyでREST APIなどからJSONデータを取得する処理を、次のようにシンプルに記述することが可能です。
require 'open-uri'
require 'json'
url = "https://exsample.com/getaddress?zipcode=7830060"
json = url.
tap {|url| break URI(url).read }.
tap {|response| break JSON.parse(response) }
nil以外の時に処理を実行
次のサンプルコードのように、変数の値がnil以外の時だけメソッドを実行するのは、よくある実装パターンです。
strA = "Ruby"
strB = nil
if !strA.nil?
p strA.upcase
end
if !strB.nil?
p strB.upcase
end
実行結果
-------------------
"RUBY"
上のサンプルコードは「tap」メソッドを使うことで、次のコードのように、もっとシンプルに書き換えることができます。
strA = "Ruby"
strB = nil
strA = strA&.tap{|x| p x.upcase}
strB = strB&.tap{|x| p x.upcase}
実行結果
-------------------
"RUBY"
このよう感じに&.演算子を使用すると、レシーバーがnilの場合、それ以降のメソッドを評価しないため、変数の値がnil以外の時だけ、指定した処理を実行したいといったケースで利用できます。
thenでもっとスッキリと
Ruby 2.6では「then」メソッドが追加されました。このメソッドは、レシーバを引数としてブロック実行し、その評価結果をメソッドの戻り値として返します。
つまり「tap」と「break」を組み合わせて実現していた処理が、「then」メソッドでbreakキーワードなしに普通に書けるようになり、さらにスッキリとしたコードになります。
value = 10
p value.then{|x| x * 2 }
実行結果
-------------------
20
先ほど紹介した、RubyでREST APIなどからJSONデータを取得するような処理も「then」を使うともっとスッキリします。
require 'open-uri'
require 'json'
url = "https://exsample.com/getaddress?zipcode=7830060"
json = url.
tap {|url| URI(url).read }.
tap {|response| JSON.parse(response) }
さいごに
普段はあまり使わないけど、知っていれば以外と便利な「tap」メソッドと、Ruby 2.6で追加された「then」メソッドの使い方を解説してきました。
特に「then」メソッドは、メソッドチェーンでさまざまな処理を繋げることができ、これまで煩雑な書かれていたプログラムコードが、「then」を使うことでスッキリとしたコードにできる可能性を秘めています。
【関連記事】
▶【Ruby入門】mapメソッドの使い方と応用例
Rubyには、配列を降順で並び替える直接的なメソッドはないため、sortで配列を昇順で並び替えた後、reverseメソッドで配列を反転させて降順でのソートを実現します。