Procとlambdaの違いとRails開発で使われる場面

## はじめに

`Proc.new`と`lambda`はどちらもProcクラスのオブジェクトを作成する方法です。

しかし、両者で作られるオブジェクトには挙動の違いがあります。

そこでこの記事では、`Proc.new`と`lambda`それぞれで生成されるProcオブジェクトの違いをまとめます。

この記事におけるバージョンは**Ruby 3.3**です。

## 【おさらい】`Proc.new`と`lambda`の使い方

最初にサラッと`Proc.new`と`lambda`の使い方を確認しておきます。

### `Proc.new`の使い方

次のように`Proc.new`を使います。

```ruby
p = Proc.new { |n| n * 2 }
p.call(5)  #=> 10
```

また`Proc.new`の代わりに`proc`メソッドを用いても、同じ機能のProcオブジェクトを生成できます。

```ruby
p = proc { |n| n * 2 }
p.call(5)  #=> 10
```

### `lambda`の使い方

`Proc.new`により生成されるProcオブジェクトと挙動は異なりますが、使い方は`lambda`も同じです。

```ruby
l = lambda { |n| n * 2 }
l.call(5)  #=> 10
```

こちらも`lambda`の代わりに`->`という記号を用いることができます。

```ruby
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つの違いがあります。

1. 引数チェックの厳格さ
2. `return`と`break`の挙動

順番に説明します。

### 違い①： 引数チェックの厳格さ

`Proc`と`lambda`は引数の数が一致しない場合の挙動が異なります。

- `Proc`: 引数の数に過不足があってもエラーが発生しない
- `lambda`: 引数の数に過不足があると`ArgumentError`が発生

`Proc`の場合、引数の数が少ない場合は足りない引数に`nil`を割り当ててくれます。

逆に数が多すぎる場合は余分な引数を無視してくれるのです。

```ruby
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つでも過不足がある場合はエラーを発生させます。

```ruby
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つの配列で複数の引数をまとめて与えることができます。

```ruby
p = Proc.new { |a, b| [a, b] }
p.call(1, 2)  #=> [1, 2]
p.call([1, 2])  #=> [1, 2]
```

`lambda`ではこの挙動が実現できません。

```ruby
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`から。
```ruby
# 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`です。

```ruby
# 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`の挙動が予期しやすいため、安全性が求められる場面で多用されているのです。

典型的な例を確認してみます。

```ruby:app/models/post.rb
class Post < ApplicationRecord
  scope :published, -> { where(published: true) }
end
```

これはActiveRecordのスコープです。

`scope`メソッドの第2引数に、`->`を使って生成された`lambda`が渡されています。

その他に、コールバックの設定や条件付きバリデーションなどでも`lambda`が使用されています。

```ruby:app/models/user.rb
class User < ApplicationRecord
  before_save -> { self.name = name.capitalize }
end
```

```ruby:app/models/order.rb
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 第２版』p94では

> lambda で作られた Proc オブジェクトは lambda と呼ばれる。もう一方は、単純に Proc と呼ばれる

と記載されています。

一方でこの呼び分けを避けている人も多いです。

`引数にProcを渡す`といった言い回しが出てきた場合に、`Proc`という表現が`Procクラスのオブジェクト`と`Proc.newで作られたオブジェクト`のどちらを指しているかが分かりにくいからです。

業務や書籍の中で`Proc`という表現が出てきたら、それが`Procクラスのオブジェクト全体`を指しているのか、それとも`Proc.newで作られたオブジェクト`を指しているのか注意深く確認し、混乱を防ぎましょう。


## 参考資料

https://docs.ruby-lang.org/ja/latest/class/Proc.html

https://www.amazon.co.jp/o/ASIN/4297124378

https://www.amazon.co.jp/dp/4873117437