【Ruby】yieldとは?使いどころを具体例を用いて解説します

以下のコードでyieldがどのように動作するか説明できますか?

yieldを用いたコード
def total(from, to)
 result = 0
 from.upto(to) do |num|
   if block_given?
     result += yield(num)
   else
     result += num
   end
 end
 return result
end

total(1, 10) { |num| num * 2 }

上のコードが説明できる方は、この記事は読む必要ありませんのでブラウザバックしてください。

説明できない、意味がわからない方は安心してください。この記事を読めば上のコードが理解できるようになります。

この記事では、yield(ブロックの概念)を解説するとともに、実際の現場での使いどころを例を交えて解説します。

対象読者
・例文のコードの動きが理解できない人
・yield見たことあるけど、理解できていない人
・Ruby初心者~中級者の人

yieldを理解するためにまずはRubyのブロックについて理解する必要があります。

Rubyのブロックとは?

Rubyではすべてのメソッド呼び出しの際、引数と一緒に「処理のかたまり」も渡すことができます。
この「処理のまとまり」のことをブロックと呼びます。

eachやmapメソッドで目にしたことはあると思いますが、do ~ end または { ~ } の中に記載されている処理のかたまりがブロックです。

Rubyでのブロック記法
# do ~ end または { ~ } どちらの表記でもOK
[1, 2, 3].each do |i|
 p i * 2
end
=> 1
   2
   3


[1, 2, 3].each { p i * 2 }
=> 1
   2
   3

この書き方はeachやmap専用の書き方ではなく、自身で実装したメソッドに対してもブロックを渡すことが可能です。

自身で実装したメソッドにもブロックを渡すことが可能
# block_methodは自身で実装したもの
block_method(10) { p 'Block called!!!'}
=> "Block called!!!"
   10

ブロックを渡せることは分かりましたが、ブロックを渡されたメソッドはどのようにブロックを実行してるんでしょう?

ここで出てくるのがyieldです!

メソッドに渡されたブロックを実行するキーワードが「yield」
def block_method(num)
 yield # 渡されたブロックを実行している正体
 p num
end

block_method(10) { p 'Block called!!!'}

yieldを実行することでメソッドに渡されたブロックを実行することができます。

yieldとは?

まず、読み方は「イールド」と読み、英語の意味は、生じる、産出する、譲渡するなど様々な意味がありますが、Rubyだと「他のものに取って代わる」という意味がしっくりきます。

メソッドにブロックが渡されている場合、yieldを実行することでメソッドに渡されたブロックを実行することができます。

yieldはメソッドのように引数を渡すことも可能です。

yieldに渡された値はブロック記法において | と | の間にはさまれた変数(ブロックパラメータ)に代入され、ブロック内で参照することが可能となります。

以下の例だと、yieldに引数numを渡しており、ブロックのblock_paramという変数で参照しています。

ブロックパラメーターの使い方
def block_method(num)
 yield(num)
 p num
end

block_method(10) {|block_param| p block_param * 2 }
=>20
  10

値の受け渡しは記法は違いますが、動きは概ね通常のメソッドと同じ考え方で良さそうです。

※注意

ブロックが渡されていない場合にyieldを実行すると、例外が発生します。

ブロックを渡さない場合にyield実行するとエラーとなる
def block_method(num)
 yield(num)
 p num
end
block_method(10)
=> LocalJumpError (no block given (yield))

ブロックを渡したり渡さなかったり柔軟に対応したいケースでは、 block_given?で評価します。

block_given?はメソッドにブロックが引き渡されていればtrueを返し、ブロックが渡されていなければfalseを返します。

以下のように利用します。

block_given?の例
def block_method(num)
 yield(num) if block_given?
 p num
end
# ブロックなし
block_method(10)
=> 10

# ブロックあり
block_method(10) { |block_param| p block_param * 2}
=>20
  10

はい。以上でyieldの説明は終わりです。

ここまでくれば、最初に提示したコードがどんな動きをし、実行結果がどうなるかわかるかと思います。

Ruby
def total(from, to)
 result = 0
 from.upto(to) do |num|
   if block_given?
     result += yield(num)
   else
     result += num
   end
 end
 return result
end

total(1, 10) { |num| num * 2 }
=> 110

total(1, 10)
=> 55

動きはわかったけど、で、実際の現場ではどんな場面でyieldを使えばいいの?

yieldの使いどころ

yield(ブロック)は使い方によっては色々できますが、現場では以下のケースでよく利用されます。

– メソッドを利用者が柔軟に拡張出来るようにする(処理の一部差し替えを行う)
– 処理の一部を利用者に委ねる

それぞれ具体例を交えて解説します。

メソッドを利用者が柔軟に拡張出来るようにする

例えば、デフォルトだと、渡された2つの和を計算するメソッドだけど、ブロックを渡すことで、独自の計算をすることができるcalculationメソッドは下のようになります。

メソッドの拡張
# ブロックが渡されていればその計算式を実行し、ブロックがなければ足算を行う
def calculation(first, second)
 block_given? ? yield(first, second) : first + second
end

calculation(10, 20)
=> 30

calculation(10, 20) {|first, second| first * second }
=> 200

calculation(10, 20) {|first, second| second / first }
=> 2

「キッチンと具材と最低限の調味料は準備したので、あとは好きなように料理を作ってくれ。必要があれば追加の具材や調味料を加えてもOK」という感じです。

処理の一部を利用者に委ねる

例えば、スクレイピングをする際には画面遷移毎に「描画の完了待ち」→「なんらかの処理」という流れで処理が行われます。

スクレイピングでよくあるロジック
def login_page
 get 'https://engineer-negoto.com/'  # URLにアクセス

 wait_loading # 画面描画の完了まち
 check_error_page  # 画面読み込み後、エラー画面が表示されていた場合のハンドリング

 # ID, PWを入力しログインボタンをクリック
 input_id
 input_password
 click_button('login')
end

def top_page
 wait_loading # 画面描画の完了まち
 check_error_page  # 画面読み込み後、エラー画面が表示されていた場合のハンドリング

 # 詳細画面へ遷移できるのボタンをクリックする
 click_button('detail')
end

def detail_page
 wait_loading # 画面描画の完了まち
 check_error_page  # 画面読み込み後、エラー画面が表示されていた場合のハンドリング

 download_html # HTMLをダウンロードする
end

コードには画面の描画待ちとエラーチェックが重複して冗長なコードとなっています。

これは下のように置き換えることができ、コードの重複がなくなり、拡張性を持たせることができます。

yieldを用いて重複をなくし、拡張性を持たせる
def process_page(url = nil)
 get url if url  # URLにアクセス

 wait_loading # 画面描画の完了まち
 check_error_page  # 画面読み込み後、エラー画面が表示されていた場合のハンドリング

 yield if block_given?
end

def login_page
 process_page('https://engineer-negoto.com/') do
   # ID, PWを入力しログインボタンをクリック
   input_id
   input_password
   click_button('login')
 end
end

def top_page
 # 詳細画面へ遷移できるのボタンをクリックする
 process_page { click_button('detail') } 
end

def detail_page
 # HTMLをダウンロードする
 process_page { download_html }
end

まとめ

  • yieldはメソッドに渡されたブロックを実行するキーワード

yieldの使い方と使いどころが理解できたかと思います。

Rubyのブロックはロジックに汎用性を持たすことができるため、是非理解して使いこなしてみてください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です