はじめに
Proc.new
とlambda
はどちらもProcクラスのオブジェクトを作成する方法です。
しかし、両者で作られるオブジェクトには挙動の違いがあります。
そこでこの記事では、Proc.new
とlambda
それぞれで生成されるProcオブジェクトの違いをまとめます。
この記事におけるバージョンはRuby 3.3です。
【おさらい】Proc.new
とlambda
の使い方
最初にサラッとProc.new
とlambda
の使い方を確認しておきます。
Proc.new
の使い方
次のようにProc.new
を使います。
p = Proc.new { |n| n * 2 }
p.call(5) #=> 10
またProc.new
の代わりにproc
メソッドを用いても、同じ機能のProcオブジェクトを生成できます。
p = proc { |n| n * 2 }
p.call(5) #=> 10
lambda
の使い方
Proc.new
により生成されるProcオブジェクトと挙動は異なりますが、使い方はlambda
も同じです。
l = lambda { |n| n * 2 }
l.call(5) #=> 10
こちらもlambda
の代わりに->
という記号を用いることができます。
l = ->(n) { n * 2 }
l.call(5) #=> 10
Proc.new
とlambda
の違い
冒頭で書いたように、Proc.new
とlambda
はどちらもProc
クラスのオブジェクトを生成します。
しかし生成されるオブジェクトには挙動の異なる点がいくつかあります。
そのため、次のように呼称を区別するケースがしばしば見受けられます。
-
Proc.new
で生成されたProcクラスのオブジェクト:Proc
と呼ばれる -
lambda
で生成されたProcクラスのオブジェクト:lambda
と呼ばれる
Proc
とlambda
には大きく分けて2つの違いがあります。
- 引数チェックの厳格さ
-
return
とbreak
の挙動
順番に説明します。
違い①: 引数チェックの厳格さ
Proc
とlambda
は引数の数が一致しない場合の挙動が異なります。
-
Proc
: 引数の数に過不足があってもエラーが発生しない -
lambda
: 引数の数に過不足があるとArgumentError
が発生
Proc
の場合、引数の数が少ない場合は足りない引数にnil
を割り当ててくれます。
逆に数が多すぎる場合は余分な引数を無視してくれるのです。
p = Proc.new { |a, b| [a, b] }
p.call(1) #=> [1, nil]
p.call(1, 2) #=> [1, 2]
p.call(1, 2, 3) #=> [1, 2]
一方でlambda
は引数のチェックがより厳格です。
1つでも過不足がある場合はエラーを発生させます。
l = lambda { |a, b| [a, b] }
l.call(1) #=> wrong number of arguments (given 1, expected 2) (ArgumentError)
l.call(1, 2) #=> [1, 2]
l.call(1, 2, 3) #=> wrong number of arguments (given 3, expected 2) (ArgumentError)
Proc
には1つの配列で複数の引数を与えることができる
Proc
の場合は、次のように1つの配列で複数の引数をまとめて与えることができます。
p = Proc.new { |a, b| [a, b] }
p.call(1, 2) #=> [1, 2]
p.call([1, 2]) #=> [1, 2]
lambda
ではこの挙動が実現できません。
l = lambda { |a, b| [a, b] }
l.call(1, 2) #=> [1, 2]
l.call([1, 2]) #=> wrong number of arguments (given 1, expected 2) (ArgumentError)
このようにProc
はより柔軟な引数の扱いが可能です。
一方でlambda
の方が厳格であるため、予期せぬ動作を防ぎやすいと言えます。
違い②: return
とbreak
の挙動
Proc
とlambda
の中でreturn
やbreak
をした場合の挙動も異なります。
-
Proc
:Proc
を呼び出したメソッド全体の処理が終了する -
lambda
:lambda
の実行だけが終わり、呼び出し元のメソッドには影響しない
これは実際のコードで確認した方がわかりやすいと思います。
まずはProc
から。
# Proc.new の中で return すると proc_return_method 全体が終了する
def proc_return_method
p = Proc.new { return "Procから抜けます" }
p.call
"メソッドの終わりに到達しました"
end
proc_return_method #=> Procから抜けます
# Proc.new の中で break すると LocalJumpError が発生する
def proc_break_method
p = Proc.new { break }
p.call
"メソッドの終わりに到達しました"
end
proc_break_method #=> break from proc-closure (LocalJumpError)
このようにProc
の中でreturn
すると呼び出し元のメソッドの処理も終了します。
break
をした場合はLocalJumpError
が発生します。
続いてlambda
です。
# lambda の中で return すると lambda_return_method 自体は続行する
def lambda_return_method
l = lambda { return "Procから抜けます" }
l.call
"メソッドの終わりに到達しました"
end
lambda_return_method #=> メソッドの終わりに到達しました
# lambda の中で break すると lambda_break_method 自体は続行する
def lambda_break_method
l = lambda { break }
l.call
"メソッドの終わりに到達しました"
end
lambda_break_method #=> メソッドの終わりに到達しました
lambda
の場合はreturn
やbreak
をしても呼び出し元のメソッドには影響せず、そのまま処理が続きます。
そのため、Proc
と比べてlambda
の方がより通常のメソッドと似た感覚で使えると言えます。
Rails開発でProcオブジェクトが使われる場面
Rails開発においてはProcオブジェクト、特にlambda
が広く使われています。
lambda
は引数の数に厳格であり、return
やbreak
の挙動が予期しやすいため、安全性が求められる場面で多用されているのです。
典型的な例を確認してみます。
class Post < ApplicationRecord
scope :published, -> { where(published: true) }
end
これはActiveRecordのスコープです。
scope
メソッドの第2引数に、->
を使って生成されたlambda
が渡されています。
その他に、コールバックの設定や条件付きバリデーションなどでもlambda
が使用されています。
class User < ApplicationRecord
before_save -> { self.name = name.capitalize }
end
class Order < ApplicationRecord
validates :card_number, presence: true, if: -> { payment_type == 'credit_card' }
end
このように、Rails開発では主にlambda
がいたる所で活用されています。
おわりに
この記事ではProc.new
とlambda
で生成されるProcクラスのオブジェクトの挙動の違いについてまとめました。
またRails開発においても、モデルを中心に様々な場面で主にlambda
が活用されていることを確認しました。
間違いなどありましたら、ご指摘いただけると幸いです。
【09/01追記】挙動の異なる2種類のProcオブジェクトの呼称について
コメントいただきましたので追記します。
記事の中で以下の内容を書きました。
-
Proc.new
とlambda
はどちらもProcクラスのオブジェクトを生成する。 - ただし挙動が異なるため、
Proc.new
で作られたオブジェクトはProc
、lambda
で作られたオブジェクトはlambda
と呼称を区別することがある
たとえば『メタプログラミングRuby 第2版』p94では
lambda で作られた Proc オブジェクトは lambda と呼ばれる。もう一方は、単純に Proc と呼ばれる
と記載されています。
一方でこの呼び分けを避けている人も多いです。
引数にProcを渡す
といった言い回しが出てきた場合に、Proc
という表現がProcクラスのオブジェクト
とProc.newで作られたオブジェクト
のどちらを指しているかが分かりにくいからです。
業務や書籍の中でProc
という表現が出てきたら、それがProcクラスのオブジェクト全体
を指しているのか、それともProc.newで作られたオブジェクト
を指しているのか注意深く確認し、混乱を防ぎましょう。
参考資料