rubyのblockについて

## 概要

なんとなくdo endを使う ということは分かっている人は多いと思いますが、
procとかが出てくると少しハードルが上がるため理解していない人が多かったように思います。
そのため改めて自分の知識の整理とともに、
多くの人がrubyの文法をしっかりと理解し効率よくコーディングができるようにまとめました。

## blockとは

- rubyではメソッドに対して一連の手続きを渡すことができる
- blockは `do end` もしくは ` {  }` で記載する
- 引数は `|x|`のように`|`ではさんだところに通常のメソッドとかと同じように記載する

```ruby
[*1..3].each do |i|
  p i
end
# => 1
# => 2
# => 3

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

## blockを使うメソッドのつくり方

- blockを実行するには **yieldを使用する** or **&blockを使用する（後述）**
- blockに仮引数を使用している場合はyieldに引数を渡すことで使用できる
- yieldは何回も使用できる
- yieldはblockが渡されていない場合に使用するとエラーになる

```ruby
def three_times
  3.times { yield }
end
three_times { p 'a' }
# => "a"
# => "a"
# => "a"

def three_times
  3.times { |j| yield(j) }
end
three_times { |i| p i }
# => 0
# => 1
# => 2

def calculate
  p yield(1, 2)
  p yield(2, 3)
end

calculate { |a, b| a + b }
# => 3
# => 5
calculate { |a, b| a * b }
# => 2
# => 6

def three_times(&block)
  3.times { yield } rescue p $!
end
three_times
# => #<LocalJumpError: no block given (yield)>
```

## blockが渡されたかどうかを判断する

- block_given?を使用する

```ruby
def three_times
  p block_given?
end

three_times
# => false
three_times { p 'y' }
# => true
```

## blockをインスタンスとして扱いたい

- blockは`&`を使用することでProcオブジェクトと相互変換できる
  - メソッドの引数として`&proc_object`をすることで、ブロックとして渡せる
  - メソッドの仮引数として`&block`とすることで、 **ブロックをProcオブジェクトとして受け取れる**
    - 引数で`&`を使わず素のblockを受け取る場合は、lambdaではなくprocになる（procとlambdaの違いは後述）
    - 引数でlambdaを`&`で渡した場合はlambdaとして受け取る

```ruby
proc_object = Proc.new do |a|
  p a
end
3.times(&proc_object)
# => 0
# => 1
# => 2

def three_times(&block)
  3.times { |j| block.call(j) }
end
three_times { |i| i }
# => 0
# => 1
# => 2
```

## procとlambda

- Procオブジェクトには`Proc`と`Proc(lambda)`というものが存在する（procとlambdaとそれぞれ呼ぶこととする）
  - どちらも`Proc`クラス
- procは`Proc.new { |x| }` もしくは `proc { |x| }`と書く
- lambdaは`lambda { |x| }` もしくは `->(x) {  }`と書く

```ruby
p Proc.new {}
# => #<Proc:0x00007f3e59459978@/tmp/nvimaASrm8/2:1>
p Proc.new {}.instance_of?(Proc)
# => true
p lambda {}
# => #<Proc:0x00007f3e59459838@/tmp/nvimaASrm8/2:2 (lambda)>
p lambda {}.instance_of?(Proc)
# => true
```

### procとlambdaの違い

#### 引数の扱い

- 引数が余分な場合
  - proc: スルーされる
  - lambda: エラーになる
- 引数が足りない場合
  - proc: nilとなる
  - lambda: エラーになる

```ruby
x = proc { |a, b| p [a,b] }
x.call(:a, :b, :c)
# => [:a, :b]

x = lambda { |a, b| p [a,b] }
x.call(:a, :b, :c) rescue p $!
# => #<ArgumentError: wrong number of arguments (given 3, expected 2)>

x = proc { |a, b| p [a,b] }
x.call(:a)
# => [:a, nil]

x = lambda { |a, b| p [a,b] }
x.call(:a) rescue p $!
# => #<ArgumentError: wrong number of arguments (given 1, expected 2)>
```

#### break, returnの挙動

- Procを定義したメソッド内で実行した場合
  - return
      - proc: Procを囲むメソッドの結果となる
      - lambda: Procの結果となる
  - break
      - proc: LocalJumpErrorになる
      - lambda: Procの結果となる
- Procを定義したメソッドの外で実行した場合
  - return
      - proc: LocalJumpErrorになる
      - lambda: Procの結果となる
  - break
      - proc: LocalJumpErrorになる
      - lambda: Procの結果となる

**手続きの結果を返したいならlambdaでもprocでも`next`を使えばよし!**

```ruby
def hoge
  x = proc { return 2 }.call
  p "x: #{x}"
  return 1
end
p "method: #{hoge}"
# => "method: 2"

def hoge
  x = lambda { return 2 }.call
  p "x: #{x}"
  return 1
end
p "method: #{hoge}"
# => "x: 2"
# => "method: 1"

def hoge
  x = proc { break 2 }.call
  p "x: #{x}"
  return 1
end
p "method: #{hoge}" rescue p $!
# => #<LocalJumpError: break from proc-closure>

def hoge
  x = lambda { break 2 }.call
  p "x: #{x}"
  return 1
end
p "method: #{hoge}"
# => "x: 2"
# => "method: 1"

def hoge(x)
  p "x: #{x.call}"
  return 1
end
x = proc { break 2 }
p "method: #{hoge(x)}" rescue p $!
# => #<LocalJumpError: break from proc-closure>

x = lambda { break 2 }
p "method: #{hoge(x)}"
# => "x: 2"
# => "method: 1"
```