一から勉強させてください

最下級エンジニアが日々の学びをアウトプットしていくだけのブログです

procとlambdaの挙動の違い

Ruby の Proc オブジェクトはKernel#proc(またはProc#new)で作るパターンとKernel#lambdaで作るパターンがありますが、それらは地味に結構挙動が違っていたりします。

そこで今回はこのKernel#procKernel#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 のreturnProc#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#procKernel#lambda でつくった時の Proc オブジェクトの主な違いです。全体的に lambda でつくった Proc オブジェクトのほうがよりメソッドぽい雰囲気がしますね。

参考は安定のこちら。いつもありがとうございます。