RubyのBlock、Proc、Lambdaの概念と違い

Rubyは柔軟性と表現力に富んだプログラミング言語であり、その強力な機能の一つに「クロージャ」があります。クロージャとは、その周囲の環境（スコープ）を「閉じ込める」ことができる関数のことです。Rubyにおけるクロージャには主に3つの形式があります：**Block**、**Proc**、および**Lambda**。これらは似ているようでいて、実は重要な違いがあります。この記事では、これらの違いを明確にし、それぞれの使用例を通じて理解を深めます。

## Blockの基本
BlockはRubyで最もよく使われるクロージャの形式です。Blockは `{}` または `do...end` で定義され、メソッド呼び出しに際して、これを引数として渡すことができます。引数を渡すには、`{}`または`do`の後で引数リストを`|`で囲みます。



**例：**

```ruby
[1, 2, 3].each { |number| puts number }
```

この例では、each メソッドにBlockが渡され、配列の各要素が出力されます。

### メソッドにブロックを渡す

ブロックはメソッドを呼び出す時のみ記述でき、メソッドの内部で`yield`を使用することでブロックの内部で記述した処理が呼び出せます。`yield`があるのにブロックが渡されない場合にはエラーが発生します。

```ruby
def func(num)
  num + yield
end

func(2) { 3 } # => 5
func(2) # => LocalJumpError: no block given (yield)
```

### メソッドにブロックが渡されたかどうかの確認

メソッドにブロックが渡されたかどうかの確認は、`block_given?`を使います。

```ruby
def func
  p block_given?
end

func { 'hoge' } # => true
func # => false
```

### ブロックに引数を渡す

ブロックに引数を渡すこともできます。

```ruby
def func(a, b)
  a + yield(b, 1)
end

p func(1, 2) { |x, y| x + y } # => 4
```

ちょっと複雑ですが、`func`に`1`と`2`を渡します。`func`の中では、第１引数の値１とブロックの実行結果を合計します。`yield`はブロック引数の２と１を合計して３を返します。従って実行結果は４になります。

## Procの基本

Procはブロックをオブジェクト化したものです。ProcはProcクラスのコンストラクタにブロックを指定して生成します。そして実行するにはProcのインスタンスに対して`call`メソッドを呼びます。

```ruby
pr = Proc.new { p 'hoge' }
pr.call # => 'hoge'
```

`Proc.new`の代わりに`proc`で定義することもできます。

```ruby
pr = proc { p 'hoge' }
```

### Procに引数を渡す

Procオプジェクトを作成する時に、仮引数を設定することができます。

```ruby
pr = proc { |arg| p arg }
pr.call('hoge') # => 'hoge'
```

### BlockとProcを相互に変換する方法

#### Blockへの変換

Procオブジェクトに`&`を付けて、最後の引数に指定するとブロックに変換されます。

```ruby
def func(x)
  x + yield
end

pr = proc { 5 }
func(2, &pr) # => 7
```

#### Procへの変換

ブロックをProcオブジェクトとして受け取るには、最後の仮引数に`&`を付けます。参考時には`&`を付けないようにご注意ください。

```ruby
def func(x, &pr)
  x + pr.call
end

func(10) { 5 } # => 15
```

## Lambdaの基本

lambdaメソッドはProcインスタンスを生成するが、Procと異なる動きがあります。

```ruby
lmb = ->(x) { p x }
lmb.call('hoge') # => "hoge"
```

### Procとlambdaの違い

Procとlambdaの違いは主に以下の２点です。lambdaによって作成されたものの方がメソッドに近い動きです。

#### 相違点１：引数のチェック

Procの場合は、渡す引数が多いと先頭からの順番で取ります、渡す引数が少ないと足りない部分に`nil`を割り当てます。

```ruby
pr = proc { |a, b| p a, b }
pr.call          # => [nil, nil]
pr.call(1)       # => [1, nil]
pr.call(1, 2)    # => [1, 2]
pr.call(1, 2, 3) # => [1, 2]
```

lambdaの場合は、メソッドみたいに、引数の数が違うと例外が発生します。

```ruby
lmb = ->(a, b) { p a, b }
lmb.call(1)    # => ArgumentError: wrong number of arguments (given 1, expected 2)
lmb.call(1, 2) # => [1, 2]
```

#### 相違点２：リターンの挙動

Proc中の`return`は生成元のスクープを脱出します。（メソッド自体を抜けます）

```ruby
def func
  pr = proc { return p 'hoge' }
  pr.call
  p 'fuga' # これは実行されない
end

func # => 'hoge'
```

lambda中の`return`はそのブロック内でリターンすると呼出元に復帰します。（メソッドに戻り、メソッドの最後まで実行します）

```ruby
def func
  lmb = -> { return p 'hoge' }
  lmb.call
  p 'fuga' # ここは実行されます
end

func # => 'hoge'
     # => 'fuga'
```

## 結論

| 特徴                   | Block                                       | Proc                                    | Lambda                                   |
|------------------------|---------------------------------------------|-----------------------------------------|------------------------------------------|
| 定義方法          | `{}` または `do...end`                      | `Proc.new` または `proc`                | `lambda` または `->`                     |
| 引数のチェック     | チェックしない                             | チェックしない                         | チェックする                             |
| リターンの挙動     | メソッドから抜け出す                         | メソッドから抜け出す                   | 呼び出し元に戻る                         |
| 呼び出し方法       | 自動で呼び出される                          | `call` メソッドを使用する               | `call` メソッドを使用する                |
| 主な使用例         | 簡単なイテレーションやコールバック            | 複数回再利用可能なクロージャ           | 明示的なクロージャと引数チェックが必要な場合 |
| 作成されたオブジェクト | 作成されない                                | `Proc` クラスのインスタンス              | `Proc` クラスのインスタンス（サブタイプ） |

RubyのBlock、Proc、Lambdaはコードの柔軟性と抽象化を高める強力なツールです。それぞれの特性を理解し、適切な場面で使用することで、効率的かつ表現力豊かなコードを書くことができます。