【Ruby】ブロック・Proc・lambda を理解する

# 概要

Rubyのブロック・Proc・Lambdaを理解する上で必要な基礎知識をまとめました。

# ブロックとは？
ブロックとは__`do~end`もしくは`{}`で囲まれた処理のカタマリの事を指します。__
以下のコードの`do |a| p a end`, `{ |a| p a }`がブロックと呼ばれる部分です。

```ruby
3.times do |a|
  p a 
end
#=> 0 1 2

3.times { |a| p a }
#=> 0 1 2
```

## メソッドにブロックを渡す
メソッドの中で`yield`を明示すると受け取ったブロックを実行します。
yieldがあるにも関わらずブロックが渡されない場合にはエラーが発生します。

```ruby 
def method1
  yield
end

method1 { p 'block' } #=> "block"
method1 #=>  `method1': no block given (yield) (LocalJumpError)
```
指定したメソッドにブロックが渡されたかどうかは`block_given?`を利用する事で確かめる事ができます。

```ruby
def method1
  p block_given?
end

method1 { p 'block' } #=> true
method1 #=> false
```
ここまでの知識を応用して、ブロックが渡された時には実行し、それ以外の場合でエラーの発生をさせたくない場合は以下の様に書けます。

```ruby
def method1
  yield if block_given?
end

method1 { p 'block' } #=> "block"
method1 ##エラーが発生しない
```
また、ブロックに引数を渡す事もできます。
以下のコードのように、ブロック内で設定した仮引数(`parameter`)に対して`yield`から実引数（`'arg'`)を渡す事が可能です。

```ruby
def method2
  yield('arg')
end

method2 { |parameter| p parameter } #=> "arg"
```

# Procとは？
- Procとは__ブロックを持ち運び便利な`オブジェクト`にしたもの__です。
- Procはクラスなので`Proc.new`でオブジェクトを作る事が出来ます。
- `Proc.new`によって作成されたProcオブジェクトはcallで呼び出すことが出来ます。

```ruby
proc1 = Proc.new { p "proc" } 
##Proc.newによってブロック（{ p "proc" })をオブジェクト化したproc1を作成。

proc1.call #=> "proc"
##Proc.newによって作成されたproc1を.callで呼び出す。
```
Procオブジェクト作成の際に以下のように仮引数を設定する事が可能です。

```ruby
proc1 = Proc.new { |par1| p par1 }
proc1.call("arg") #=> "arg"
```

## ブロックを引数で明示的に受け取る方法
メソッドにブロックを渡す方法として`yield`を紹介ましたが、下記コードのように`&`から始まる`ブロック引数`を使用する事でブロックを引数で明示的に受け取ることも可能です。

下記コードでは以下のような工程を経ています。

- __`&`が付いた引数(ブロック引数)に渡されたブロックを`Procオブジェクト`に変換する。（ブロックのオブジェクト化）__
- __`procオブジェクト.call`によって渡されたオブジェクトを呼び出す。__


```ruby
def method2(&proc) ##ブロックのオブジェクト化
  proc.call ##Procオブジェクトの呼び出し
end

method2 { p 'block' } #=> "block"
```

# lambdaとは？
`lambda`とはProcオブジェクトを作る方法の一つで、lambdaメソッドによって作成されたProcオブジェクトは`Proc.new`で作成されたオブジェクトといくつか異なる点を持ちます。
Procの説明部分でProcオブジェクトを作成する方法の１つである`Proc.new`を紹介しましたが、実際には以下の様な複数の方法が存在します。`lambda`もその内の１つです。

- `Proc.new{ }` による方法
- `proc{ }` による方法
- `lambda{ }` による方法
- `->{ } `による方法(lambdaによって作成されたProcオブジェクトと同じ性質をもつオブジェクトを作成する。）

```ruby
proc1 = Proc.new { |arg| puts arg } ## Proc.new{ } による方法
proc2 = proc { |arg| puts arg } ##proc{ } による方法
proc3 = lambda { |arg| puts arg } ##lambda{ } による方法
proc4 = ->(word) { puts word } ##->{ } による方法
proc5 = -> { puts "proc" } ##->{ } による方法
```

## Proc.newとlambdaの相違点
`lambda`によって作成されたProcオブジェクトと`Proc.new`によって作成されたProcオブジェクトの相違点は主に以下の２点です。`lambda`によって作成された物の方がよりメソッドに近い働きをします。

- __引数チェック__
- __returnの挙動__

### 相違点①（引数チェック）

__Proc.newの場合__
`Proc.new`の場合は渡す引数が多いと、先頭から必要な数だけ取って後は無視し、少ないと足りない部分に nil を割り当てるため多重代入に近い扱い方をします。

```ruby
proc1 = proc { |arg| p arg }
proc1.call( 'proc', 'lambda') #=> "proc"
```

__lambdaの場合__
`lambda` の場合は引数の数が違うと__ArgumentError__になります。

```ruby
lamd = lambda { |arg| p arg }
lamd.call('lambda') #=> "lambda"
lamd.call('lambda', 'proc') #=>  wrong number of arguments (given 2, expected 1) (ArgumentError)
```

###相違点②（returnの挙動）
__Proc.newの場合__
Proc.newの場合はreturn後にメソッド自体を抜けてしまいます。

```ruby
def proc_method
  proc = Proc.new { return p "proc"}
  proc.call
  p "proc method"
end

proc_method #=> "proc"
```

__lambdaの場合__
lambdaの場合はreturnした後にメソッドに戻り、メソッドを最後まで実行します。

```ruby
def lambda_method
  lambda1 = lambda { return p "lambda"}
  lambda1.call
  p "lambda method"
end

lambda_method #=> "lambda"
              #=> "lambda method"
```

# 参考
[Ruby block/proc/lambdaの使いどころ](https://qiita.com/kidach1/items/15cfee9ec66804c3afd2)
[【Ruby】ブロックとProcとlambdaについて](http://www.task-notes.com/entry/20170413/1492094820)
[Rubyの ブロック、Proc.new、lambdaの違い](https://qiita.com/ryo-ma/items/24c46592b45775e8644d)