yieldとProcの理解が難しかったので、「プロを目指す人のためのRuby入門」やQiita記事を参考にして学習したことを復習がてらまとめてみた。
学習を進める上で、過不足がでてきたら、随時更新してブラッシュアップしていきます。
Contents
yieldについて
大前提として、押さえておかないことは、メソッドは引数にブロックを取ることができるということ。
でも、引数に長々とブロックの記述なんかしてられない。そこで、登場するのが「yield」
yieldは超簡単に言うと、ブロック(do 〜 end/ { } )の処理のかたまりを置き換えた変数みたいなもの。yieldのおかげで、引数にブロックを取ってもスッキリとしたコーディングになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def greeting puts 'good morning' if block_given? #block_given?メソッドでブロックが渡されているかを判定 yield end #yield = do 〜 endのブロック内にある処理。つまり、puts 'good afternoon' puts 'good evening' end greeting do puts 'good afternoon' #greetingがメソッド名、do 〜 endが引数(ブロック) end #=> good morning # good afternoon # good evening |
yieldはブロックに引数を渡したり、ブロックの戻り値を受け取ったりすることができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def greeting puts 'good morning' str = yield 'good afternoon' #do 〜 endのブロックに対して'good afternoon'という引数を渡して処理をしている。 puts str puts 'good evening' end greeting do |str| str * 2 end # good morning # good afternoongood afternoon # good evening |
ブロックを引数として明示的に受け取る
これまでの書き方だとメソッド内のyieldにたどり着くまでブロックが引数になっているということが分かりにくかったが、ブロックを引数として明示させることができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def greeting(&block) #引数に明示的にブロックを記述する際は「&」が必須。「&」で引数のブロックをProcオブジェクトに変換している。 puts 'good morning' str = block.call('good afternoon') #block.callで引数で受け取ったブロックを呼び出し、ブロックに'good afternoon'という引数を渡して処理。 puts str #.callメソッドはProcクラスのインスタンスメソッド。引数の受け取りときにProcオブジェクトに変換しているから使うことができる。 puts 'good evening' end greeting do |str| str * 2 end # good morning # good afternoongood afternoon # good evening |
Procについて
Procの基本
Procクラスはブロックをオブジェクト化するためのクラス、つまり、「何らかの処理(手続き)」を表す。
実行したいときはcallメソッドを使って呼び出す。Procオブジェクトの実行方法は4つある。
1 2 3 4 5 6 7 8 9 10 11 12 |
proc = Proc.new { |a,b| a +b } #callメソッドを使う proc.call(10,20) #yieldメソッドを使う proc.yield(10,20) #.( )を使う proc.(10,20) #[ ]を使う proc[10,20] #いずれも結果は30 |
Procの良いところは、ブロックをオブジェクト化しているので、変数に入れて他のメソッドに渡したり、Procオブジェクトに対してメソッドを呼び出すことができる点にある。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def greeting(&block) #ここで受け取っているblockはprocに代入されているProcオブジェクト puts 'good morning' str = block.call('good afternoon') #Procオブジェクトを「.call」で呼び出し、'good afternoon'を引数として渡す puts str puts 'good evening' end proc = Proc.new { |text| text * 2 } #Procオブジェクトをproc変数に代入 greeting(&proc) #proc変数を引数にする #good morning #good afternoon #good evening |
上記の分では、greetingに渡している仮引数に「&」が付いているが、もとのproc変数がProcオブジェクト。よって、ここでは別に「&」でProcオブジェクトに変換する必要がないので、「&」はなくてもOK。
メソッドが引数として受け取れるブロックの数は1つだが、Procオブジェクトはもはやブロックではなく、オブジェクトなので、引数として何個も渡すことができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def greeting(proc_1, proc_2, proc_3) puts proc_1.call('good morning') puts proc_2.call('good afternoon') puts proc_3.call('good evening') end proc_1 = Proc.new { |text| text } proc_2 = Proc.new { |text| text * 2 } proc_3 = Proc.new { |text| text * 3 } greeting(proc_1, proc_2, proc_3) #good morning #good afternoongood afternoon #good eveninggood eveninggood evening |
個人的にはProcオブジェクトを生成してから、メソッドに引数として受け渡せば引数の先頭に「&」を書く必要もないし、引数に渡しているオブジェクトの処理も見やすくなるので、こっちの方が好きかも。
Proc.newとlambda
先程、Procオブジェクトの4つの実行方法については記述したが、実は生成方法も4つある。
ラムダ式については、こちらを一つ参考にして理解を深めます。
1 2 3 4 5 6 7 8 9 10 11 12 |
#Proc.newを利用 Proc.new { |a, b| a + b } #procメソッドを利用 proc { |a, b| a + b } #アロー演算子(ラムダリテラル)を利用 ->(a, b) { a + b } #(a,b)は引数のリスト。{ a + b }が処理内容 #lambdaを利用 lambda { |a, b| a + b } |
結局何が違うの?
lambdaはProc.newよりも引数のチェックが厳密になるという違いがある。
1 2 3 4 5 6 7 |
proc = Proc.new { |a, b| a.to_i, b.to_i } proc.call(10) #10 proc.call(10, 20, 30) #30(引数と仮引数の数が違ってもArgumentErrorにならず、仮引数の数までの引数が代入される) lambda = -> (a, b) { a.to_i, b.to_i } lambda.call(10) #引数と仮引数の数が違うのでArgumentError lambda.call(10, 20, 30) #引数と仮引数の数が違うのでArgumentError |
クロージャ
Procオブジェクト内で引数やローカル変数を参照すると、メソッドの実行が完了した後でもProcオブジェクトは引き続き、引数やローカル変数にアクセスし続けることができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def generate_proc(array) counter = 0 Proc.new do counter += 10 array << counter end end #Procオブジェクトをメソッドの返り値として、 ローカル変数counterに加算。最後にarrayに値を追加している。 values = [] proc = generate_proc(values) proc.call values #[10] #もう一度実行すると、、、valuesの中身が前回の実行時の返り値を残したまま、メソッドが再度実行される。 proc.call values #[10, 20] |
このようにメソッドが生成されたときのコンテキスト(変数情報など)を保持しているメソッドのことをクロージャ(関数閉包)という。