Techioz Blog

ブロック内での「yield」の仕組み

概要

以下のコードを理解するのが難しいです

  module MyEnumerable
      def my_select
        new_array = []
        each do |value|
          new_array << value if yield(value)
        end
        new_array
      end
    end
    
    class Week
      include MyEnumerable
    
      def initialize
        @days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
      end
      def each
        @days.each { |day| yield day }
      end
    end
    
    week = Week.new
    puts week.my_select { |day| day.start_with?("T") }
    

プログラムの結果出力

Tuesday
Thrusday

曖昧なコード

def each
   @days.each { |day| yield day }
end

1.「yield」に指定されたブロックはありませんが、コードは引き続き機能します。

2.「yield」はブロックを与えるメソッド内で使用するものではないでしょうか?メソッド内の「yield」がブロックを呼び出すことは明らかです。

3.ブロック内の「yield」はどのように機能しますか?

「my_select」メソッドを次のようにリファクタリングしました

    def my_select
        new_array = []
        each { |value| new_array << value if yield(value) }
        new_array
      end
    end

「each」メソッドはブロックを生成します

 each { |value| new_array << value if yield(value) }  
 def each
   @days.each { |day| yield day }
 end

「my_select」メソッドはブロックを生成します

week.my_select { |day| day.start_with?("T") }
def my_select
  new_array = []
  each { |value| new_array << value if yield(value) }
  new_array
end

 

解決策

ブロックを直接渡して yield することはありません。囲んでいるメソッドを呼び出すときにブロックを渡すと、yield がそのブロックを呼び出します。例:

week = Week.new
week.each { |day| puts day }
#             │ 
#             └──── from `yield day'

はい、正確に。コード内の呼び出しは少し間接的です。 MyEnumerable とその my_select メソッドを含めて、それぞれのメソッドを呼び出します。

# ┌─────────────── this is your `each' method
# │
each do |value|
  new_array << value if yield(value)
end

実行する |値| …end は探していたブロックです。 (my_select は独自のブロックにも値を生成するため、他のブロックも生成します)

特別なことは何もありません。 @days.each { |day| yield day } は @days の各要素に対して yield を呼び出します。静的な配列の内容を想定すると、各メソッドは次のように書き換えることができます。

def each
  yield "Monday"
  yield "Tuesday"
  yield "Wednesday"
  yield "Thursday"
  yield "Friday"
end