銀行員からのRailsエンジニア

銀行員からのRailsエンジニア

銀行員から転身したサービス作りが大好きなRailsエンジニアのブログです。個人で開発したサービスをいくつか運営しており、今も新しいサービスを開発しています。転職して日々感じていること、個人開発サービス運営のことなどを等身大で書いていきます。

自己yieldについて【Ruby】

こんにちは。

最近 メタプログラミングRuby を読み印象に残るところが多かったので、1つずつブログにまとめています。

今回は第2弾で、自己yield についてまとめました。

f:id:ysk_pro:20200711095214p:plain

自己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 よくあるイディオム」に記載されていました。

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

次回もまた、メタプログラミングRubyを読んで印象に残ったところをまとめていこうと思います。

アクセスメソッドの注意点について書いた前回の記事も是非合わせてご覧ください。
ysk-pro.hatenablog.com