Ruby の Proc オブジェクトはKernel#proc
(またはProc#new
)で作るパターンとKernel#lambda
で作るパターンがありますが、それらは地味に結構挙動が違っていたりします。
そこで今回はこのKernel#proc
とKernel#lambda
でつくった時の Proc オブジェクトの挙動の違いについて見ていこうと思います。
return の時の挙動が違う
def proc_hoge proc { return 1; puts 'hoge' }.call 'hogehoge' end def lambda_hoge -> { return 1; puts 'hoge' }.call 'hogehoge' end proc_hoge #=>1 lambda_hoge #=> 'hogehoge'
こんな感じで proc のreturn
はProc#call
を読んだコンテキストから抜けます。
よって、もしトップレベルで
proc { return 1; puts 'top level!' }.call #=> LocalJumpError
とやるとLocalJumpError
となります。
一方、lambda の方はKernel#lambda
で作成したオブジェクトの制御を抜けるだけです。
トップレベルで呼び出しても問題ありません。
-> { return 1; puts 'hoge' }.call #=> 1
break の時の挙動が違う
def proc_hoge proc { break 1; puts 'hoge' }.call 'hogehoge' end def lambda_hoge -> { break 1; puts 'hoge' }.call 'hogehoge' end proc_hoge #=>LocalJumpError lambda_hoge #=> 'hogehoge'
次にbreak
の挙動ですが、proc の場合はbreak
で常にLocalJumpError
が発生します。トップレベルだろうがメソッドの中だろうがエラーです。
一方、lambda の場合はreturn
と同様、制御を抜けるだけです。
これはかなり挙動が違うので注意する必要がありそうですね。
対策としてはnext
を使うとどちらの場合でも制御を抜けるだけになるので、それを使うといいと思われます。
def proc_hoge proc { next 1; puts 'hoge' }.call 'hogehoge' end def lambda_hoge -> { next 1; puts 'hoge' }.call 'hogehoge' end proc_hoge #=> 'hogehoge' lambda_hoge #=> 'hogehoge'
引数に対する厳密さが違う
proc { |x, y| [x, y] }.call(1, 2, 3) #=> [1, 2] proc { |x, y| [x, y] }.call(1) #=> [1, nil] proc { |x, y| [x, y] }.call([1, 2]) #=> [1, 2] # lambda { |x, y| x }.call(1, 2, 3)と同じ -> (x, y) { x }.call(1, 2, 3) #=> ArgumentError -> (x, y) { x }.call(1) #=> ArgumentError -> (x, y) { x }.call([1, 2]) #=> ArgumentError
proc に引数を渡すときは
- 仮引数の数より多く引数が渡された時、余分な引数は無視する
- 引数の数が足りない時は
nil
を渡す - 配列が 1 つだけ渡されると展開される
のような特徴があります。
一方、lambda の場合は上記はすべてArgumentError
になります。なかなか手厳しいですね。
まとめ
以上がKernel#proc
とKernel#lambda
でつくった時の Proc オブジェクトの主な違いです。全体的に lambda でつくった Proc オブジェクトのほうがよりメソッドぽい雰囲気がしますね。
参考は安定のこちら。いつもありがとうございます。