一から勉強させてください( ̄ω ̄;)

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

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オブジェクトのほうがよりメソッドぽい雰囲気がしますね。

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

パーフェクトRuby (PERFECT SERIES 6)

パーフェクトRuby (PERFECT SERIES 6)