Rubyの ブロック、Proc.new、lambdaの違い

  • 208
    いいね
  • 5
    コメント
この記事は最終更新日から1年以上が経過しています。

ブロックとProc.newはそもそも別物です。
lambdaに関してはProc.newと同じようですが若干違いがあります。

この違いを理解しておかないと、ハマる可能性があります。

ブロックとProc.new

ブロックとは

まずブロックとはdo~endまたは{~}の部分で引数として渡すものです。
以下のコードでは{ puts "block" }がそれに相当します。
引数は明示的に書かなくても渡す事ができyieldでブロックを評価する事が出来ます。
おそらくlist.each{ |a| a.hoge }などで使った事があると思います。それがブロックです。

example.rb
def method_block
  yield
end
method_block { p "block" }
#実行結果
=>"block"

Proc.newとは

ブロックをオブジェクト化したものがProc.newになります。
一つ前のコードでメソッドの引数を明示的に書く事が出来ます。
この場合は&によりブロックがメソッドに渡ってきたときにProcオブジェクトになります。
&は引数にブロックが渡ってきたときにProcオブジェクトに変換します。
Procオブジェクトはcallメソッドによって評価する事が出来ます。

example.rb
def method_block (&block)
  block.call
end
method_block { p "block" }
#実行結果
=>"block"

ProcはクラスなのでProc.newでオブジェクトを作る事が出来ます。

example.rb
def method_proc
  proc = Proc.new { p "proc" }
  proc.call
end
method_proc
#実行結果
=>"proc"

引数を使う場合

{ |a,b,c|~ }のa,b,cが引数にあたいします。
callの引数に対応したデータを渡して上げます。

example.rb
proc = Proc.new{|a| p a * 2 }
proc.call(2)
#実行結果
=>4

違い

Rubyではほぼすべてがオブジェクトですがブロックはオブジェクトではありません
そしてブロックをオブジェクトとしたものがProc.newです

Proc.newとlambda

lambdaとは

まずlambdaの例を挙げて行きます。
lambda{~}でlambdaを使う事でき変数に代入する事も出来ます。
これはProcのインスタンスを生成して返却しているからです。
なので評価もcallメソッドで行えます。
では一体なにがProc.newと違うのか。

example.rb
def method_lambda
  lambda1 = lambda{ p "lambda" }#Proc.new が返却
  lambda1.call
end
method_lambda
#実行結果
=>"lambda"

違い1つ目

lambdaはブロックの引数の数が違うとエラーを起こしてくれます。

Proc.newで数の違う引数を渡す場合

このように足りない引数の部分はnilが入ります。

example.rb
proc = Proc.new{ |a,b,c| p "#{a},#{b},#{c}" }
proc.call(2, 4)
#実行結果
=> 2,4,nil

lambdaで数の違う引数を渡す場合

このようにエラーが発生します。
なのでlambdaのほうが厳密となります。

example.rb
lambda1 = lambda{ |a,b,c| p "#{a},#{b},#{c}" }
lambda1.call(2, 4)
#実行結果
=> wrong number of arguments (2 for 3) (ArgumentError)

違い2つ目

明示的にreturnやbreakを行った場合の挙動が違います。

Proc.newの場合

example.rb
def method_proc
  proc = Proc.new { return p "proc"}
  proc.call
  p "method_proc"
end
#実行結果
=>"proc"

lambdaの場合

example.rb
def method_lambda
  lambda1 = lambda{ return p "lambda"}
  lambda1.call
  p "method_lambda"
end
method_lambda
#実行結果
=>"lambda"
  "method_lambda"

違いが分かりましたよね?
Proc.newの方はcallメソッドを実行した後の処理が評価されてません。それに対してlambdaの方は最後まで評価されています。
これはProc.newの場合、returnを書くとmethod_procメソッド自体を抜けてしまうからです。lambdaの方はreturnした後、method_lambdaメソッドに戻ります。breakの場合、Proc.newでは例外が発生します。lambdaの方は同じように手続きオブジェクトを抜けます。
ただし明示的にreturnを書かなければその後も評価されます。

まとめ

ブロックはオブジェクトではありません。
ブロックをオブジェクトとしたものがProc.newです。
Proc.newに対してlambdaの方が引数に関しては厳密です。
returnなどの動作に関してはProcとlambdaでは異なりlambda方はメソッドに近い動きです。

特にProc.newとlambdaの違いは、ハマる可能性があるので覚えておくといいと思います。