【Ruby】Procの基礎を理解する

# Procクラス
https://docs.ruby-lang.org/ja/latest/class/Proc.html

- ブロックをオブジェクトとして扱いたい時に使用するクラス
  - ブロック
    - メソッドの引数として渡すことが出来る処理のまとまり
    - `do .. end`, `{ .. }`
- Procは、クロージャに相当する

```rb
i = 100
pr = Proc.new { i += 1000 } # proc { i += 1000 } も同様

p pr.call #=> 1100
p i #=> 1100

p pr.call #=> 2100
p i #=> 2100
```


# クロージャとは

>[手続きをオブジェクトとして扱う機能があります。このオブジェクト化された手続きのことをクロージャと呼びます。](https://docs.ruby-lang.org/ja/latest/doc/spec=2fintro.html#:~:text=%E5%88%B6%E5%BE%A1%E6%A7%8B%E9%80%A0%E3%81%AE%E6%8A%BD%E8%B1%A1%E5%8C%96,%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E3%81%A8%E3%81%84%E3%81%86%E6%A9%9F%E8%83%BD%E3%81%8C%E3%81%82%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82&text=%E6%89%8B%E7%B6%9A%E3%81%8D%E3%82%92%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%A8%E3%81%97%E3%81%A6%E6%89%B1%E3%81%86,%E3%82%92%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%B8%E3%83%A3%E3%81%A8%E5%91%BC%E3%81%B3%E3%81%BE%E3%81%99%E3%80%82)

>[・クロージャとは、関数内で特定の変数を参照し続けることで、関数が状態を持てる仕組みのこと。
・クロージャは呼び出す度に、新たな関数を定義する関数ファクトリ。
・クロージャを使用することで、グローバル汚染を防ぐことができ、保守性が高まる。](https://zenn.dev/takiko/articles/5400a6b0c6bd7d#%E3%81%BE%E3%81%A8%E3%82%81)

>[クロージャとは関数とその関数が定義されている状態をセットにしたもの
・記述の省略が可能
・処理をまとめられる
・カプセル化ができる](https://one-div.com/programming/closure_meaning_examples_usage)

```rb:クロージャのイメージ
def add_num n
  yield + n
end

i = 10

p add_num(1) { i += 100 } #=> 111
p i #=> 110

p add_num(1) { i += 1000 } #=> 1111
p i #=> 1110
```

上の例だと、
- `#add_num` の引数に `1` を渡す
- `#add_num` 内の `yield` がブロックの処理を呼び出す
- ブロックの中で、ローカル変数 `i` を更新する
- 更新された変数 `i` と `n` の加算を行っている（代入はしていない）
- ブロック付きメソッドを呼び出す度に、外部の変数である `i` が更新されている

:::note
外部のローカル変数を捉え、その変数に対してアクセス・変更が可能
:::

:::note
遅延評価したい場面などで使いやすい
:::

# ブロックをProcに変換して受け取りたい場合

```rb
def calc_result(x, &block)
  # p block.class #=> Proc
  result = block.call(x)
  puts "Result: #{result}"
end

i = 100

# 加算のブロックを渡す
calc_result(900) { |n| i += n } #=> Result: 1000

# 減算のブロックを渡す
calc_result(300) { |n| i -= n  } #=> Result: 700

p i #=> 700
```

:::note
最後の引数に「&」を付けることで、ブロックをProcオブジェクトとして受け取る
:::


# `#block_given?`

>[メソッドにブロックが与えられていれば真を返します。](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/block_given=3f.html)

```rb
def calc_result(x, y, proc_object = nil, &block)
  if proc_object
    result = proc_object.call(x, y)
    puts "Using Proc: #{result}"
  elsif block_given?
    result = yield(x, y)
    puts "Using Block: #{result}"
  else
    puts "No Proc or Block specified."
  end
end

# Procオブジェクトを定義
addition = Proc.new { |a, b| a + b }

# Procオブジェクトを指定
calc_result(100, 200, addition) #=> Using Proc: 300

# ブロックを指定
calc_result(100, 200) { |a, b| a - b } #=> Using Block: -100

# Proc・ブロックのいずれも指定しない
calc_result(100, 200) #=> No Proc or Block specified.
```

# Proc `#<<` `#>>`
https://docs.ruby-lang.org/ja/latest/method/Proc/i/=3c=3c.html

https://docs.ruby-lang.org/ja/latest/method/Proc/i/=3e=3e.html

>self と引数を合成した Proc を返します。

```rb
# Procオブジェクト1: 与えられた引数に1を加える
add_one = Proc.new { |x| x + 1 }

# Procオブジェクト2: 与えられた引数を2倍する
multiply_by_two = Proc.new { |x| x * 2 }

# 左合成 (Proc#<<): 最初にmultiply_by_twoを適用し、その結果をadd_oneに渡して実行する
composed_proc_left = add_one << multiply_by_two

# 右合成 (Proc#>>): 最初にadd_oneを適用し、その結果をmultiply_by_twoに渡して実行する
composed_proc_right = add_one >> multiply_by_two

# 引数用の値
num = 3

# 左合成の結果
result_left = composed_proc_left.call(num)       # (3 * 2) + 1
puts "Left Composition Result: #{result_left}"   #=> Left Composition Result: 7

# 右合成の結果
result_right = composed_proc_right.call(num)     # (3 + 1) * 2
puts "Right Composition Result: #{result_right}" #=> Right Composition Result: 8
```



# lambda
>[Kernel.#lambda と Proc.new はどちらも Proc クラスのインスタンス(手続きオブジェクト)を生成しますが、生成された手続きオブジェクトはいくつかの場面で挙動が異なります。
lambda の生成する手続きオブジェクトのほうがよりメソッドに近い働きをするように設計されています。](https://docs.ruby-lang.org/ja/latest/class/Proc.html#lambda_proc)

```rb
l = lambda { |a, b| a - b }

p l.class #=> Proc
p l.lambda? #=> true

p l.call(10, 3) #=> 7
```

```rb
# Ruby1.9 ~ は、以下のようにも書くことが出来る
l = ->(a, b) { a - b }

p l.call(10, 3) #=> 7
```

# `proc` と `lambda` の振る舞いの違い
## 引数の扱い
:::note alert
`lambda`では、引数の個数が違うと **ArgumentError** が発生する
```rb
pr = ->(a, b) { p a, b }
pr.call(10) #=> sample.rb:1:in `block in <main>': wrong number of arguments (given 1, expected 2) (ArgumentError)
```
:::

:::note
`Proc.new`, `proc`では発生しない
```rb
pr = proc { |a, b| p a, b  }
pr.call(10)
#=> 10
#   nil
```
:::

## ジャンプ構文の挙動

```rb
def test_proc
  pr = Proc.new { return :from_proc }
  pr.call
  return :from_method
end

def test_lambda
  l = lambda { return :from_lambda }
  l.call
  return :from_method
end

def test_block
  tap { return :from_block }
  return :from_method
end

p test_proc   #=> :from_proc
p test_lambda #=> :from_method
p test_block  #=> :from_block
```

:::note warn
**Proc.new** は、ブロック内でのreturnと共に、呼び出し元のメソッドも抜けている。
**lambda**は、ブロック内でreturnをするだけで、呼び出し元の後続処理は継続する。
:::

||return|next|break|
|-|-|-|-|
|***Proc.new***|メソッドを抜ける|手続きオブジェクトを抜ける|例外が発生する|
|***proc***|メソッドを抜ける|手続きオブジェクトを抜ける|例外が発生する|
|***lambda***|手続きオブジェクトを抜ける|手続きオブジェクトを抜ける|手続きオブジェクトを抜ける|
|***イテレータ***|メソッドを抜ける|手続きオブジェクトを抜ける|手続きオブジェクトを抜ける|

:::note alert
`Proc`を生成したメソッドから脱出した後、手続きオブジェクトからの `return`, `break` は ***LocalJumpError*** が発生する。
```rb
def foo
  Proc.new { return :from_proc }
end

p foo.call #=> sample.rb:2:in `block in foo': unexpected return (LocalJumpError)
```

:::

:::note
`lambda`では発生しない
```rb
def foo
  lambda { return :from_lambda }
end

p foo.call #=> :from_lambda
```
:::