こんにちは。
最近 メタプログラミングRuby を読み印象に残るところが多かったので、1つずつブログにまとめています。
今回は第2弾で、自己yield についてまとめました。
自己yield とは
Rubyはメソッドにブロックを渡すことができます。
メソッドに渡されたブロックは、メソッド内で yield とすることで実行できます。
自己yield は、メソッド内で yield を使ってブロックを呼び出すときに、yield self としてオブジェクト自身(self)を引数としてブロックに渡すことを言います。
これがどのように使えるのか、サンプルコードを見ていきましょう。
サンプルコード
自己yield を使っていないコードを自己yield を使って書き換えていきます。
まずは、自己yield を使っていないコードです。
class PC attr_writer :name, :os, :price def initialize(name, os, price) self.name = name self.os = os self.price = price end end mac_book_air = PC.new('mac book air', 'mac', 150_000) p mac_book_air #-> #<PC:0x00007fcf05863158 @name="mac book air", @os="mac", @price=150000>
インスタンス作成時にインスタンス変数をいくつかセットするだけのシンプルなコードです。
インスタンス変数が3つだと問題はなさそうですが、数が増えると引数の順番に気をつかう必要が出て分かりづらくなりそうですね。
このコードを自己yield を使って書き換えると次のようになります。
class PC attr_writer :name, :os, :price def initialize yield self end end mac_book_pro = PC.new do |pc| pc.name = 'mac book pro' pc.os = 'mac' pc.price = 200_000 end p mac_book_pro #-> #<PC:0x00007f8015017128 @name="mac book pro", @os="mac", @price=200000>
PCクラスのインスタンスを作る際にブロックを渡しており、PCクラスの initializeメソッドでそのブロックを呼び出しています。
ブロックでインスタンス変数をセットしていることで、どのインスタンス変数にどの値を入れているか分かりやすくなりましたね。
さらに、メソッドにブロックが渡されたか判定できる block_given? メソッド を使えば、どちらのインスタンス作成の方法にも対応することができます。
class PC attr_writer :name, :os, :price def initialize(name = nil, os = nil, price = nil) if block_given? yield self else self.name = name self.os = os self.price = price end end end
実際に使われているところ
自己yield が実際に使われている例を見てみましょう。
Faraday
よく使われている HTTPクライアントライブラリ Faraday で自己yield が使われています。
Faraday は次のように、分かりやすく HTTPリクエストを行うことができます。
resp = Faraday.get('http://sushi.com/search') do |req| req.params['limit'] = 100 req.headers['Content-Type'] = 'application/json' req.body = {query: 'salmon'}.to_json end # => GET http://sushi.com/search?limit=100
このコードは 公式ドキュメント からの引用です。
ブロックを使うことで、クエリパラメータやリクエストヘッダ、ボディに直感的に値をセットすることができます。
Faraday のコード内で yield self が実行されています。
tap
tap はメソッドチェーンの途中で値をチェックしたい場合などに使われるメソッドです。
次のようによく使われます。
a = ['r', 'u', 'b', 'y'].map(&:next).pop puts a #-> z # メソッドチェーンの途中で値を確認する a = ['r', 'u', 'b', 'y'].map(&:next).tap{|x| p x}.pop #-> ["s", "v", "c", "z"] puts a #-> z
実際の tap はRubyで書かれていませんが、Rubyで実装すると自己yield を使った次のようなシンプルなコードになります。
class Object def tap yield self self end end
おわりに
ここまでお読みいただきありがとうございます。
自己yield は色々なところで使われており、ソースコードを読む際にも必要になるので、知っておいて損はないと思います。
今回紹介した自己yield はメタプログラミングRuby の「付録A よくあるイディオム」に記載されていました。
- 作者:Paolo Perrotta
- 発売日: 2015/10/10
- メディア: 大型本
次回もまた、メタプログラミングRubyを読んで印象に残ったところをまとめていこうと思います。
アクセスメソッドの注意点について書いた前回の記事も是非合わせてご覧ください。
ysk-pro.hatenablog.com