【Ruby】ブロックとyield・Proc・lambda

# はじめに

`yield`、`Proc`、`lambda`、こいつらを目にするたびに

1. 「何だったっけなぁ、ブロックを扱う概念だった気がするけど、よくわからん」
2. 調べて、軽くコードを書いて実行してみる
3. わかった気がして満足する
4. しばらく使うこともなく、1へ戻る

を繰り返していました。

そもそも「ほとんど使うことがない」というのも、理解が不十分だから使える場面を見逃しているのではないかという気もしてきたので、ここらで記事にまとめて、理解を深めていこうと思います。

実際にコードを書きながら挙動を確認していくので、似たようなことを感じている方はぜひ、手を動かしながらご覧ください！

「公式ドキュメントを読めばわかるだろ！」という話かもしれませんが、やっぱり自分でコードを書いて（さらに気になったことを思いつくまま試した方が）理解が深まると思います。

# 目次

- [検証環境](#検証環境)
- [ブロック](#ブロック)
- [yield](#yield)
- [Proc](#proc)
- [lambda](#lambda)
- [Proc.newとlambdaの違い](#procnewとlambdaの違い)
  - [引数の扱い](#引数の扱い)
  - [ジャンプ構文の挙動](#ジャンプ構文の挙動)
- [さいごに](#さいごに)

# 検証環境

Ruby 3.0.5

# ブロック

ブロックはメソッドを呼び出すときに記述できるコードのかたまりです。
`{}`または`do`と`end`で囲むことで記述できます。
一行で書く場合は`{}`、複数行で書く場合は`do`と`end`が使われることが多いです。

試しに、メソッドなしでブロックを記述してみます。

```ruby:sample.rb
do
  puts 'sample'
end
```

```:実行結果
sample.rb:1: syntax error, unexpected `do'
sample.rb:5: syntax error, unexpected `end', expecting end-of-input
```

エラーが発生し、メソッドなしでは記述できないことがわかります。

# yield

`yield`はブロックをメソッドの内部で呼び出すときに使います。

```ruby:sample.rb
def sample(x, y)
  puts "yield:#{yield}"
  x + y + yield
end

puts "sample:#{sample(1, 2) { 3 }}"
```

```:実行結果
yield:3
sample:6
```

ブロックの戻り値である3がメソッド内の`yield`に代入されていることがわかります。

さて、`yield`を記述せずにメソッドにブロックを渡すとどうなるのでしょうか？
気になったことは試してみます。

```ruby:sample.rb
def sample(x, y)
  x + y
end

puts "sample:#{sample(1, 2) { 3 }}"
```

```bash:実行結果
sample:3
```

メソッドに渡したブロックが無視されています。

エラーが発生しないので、使わないブロックを渡していても気が付きにくいので、気を付けていきたいところです。（そもそも、使わないブロックをうっかり渡す場面なんて、あんまりないか？）

では、逆に`yield`を記述して、メソッドにブロックを渡さないとどうなるのでしょうか？
早速、試してみます。

```ruby:sample.rb
def sample(x, y)
  puts "yield:#{yield}"
  x + y + yield
end

puts "sample:#{sample(1, 2)}"
```

```:実行結果
sample.rb:2:in `sample': no block given (yield) (LocalJumpError)
        from sample.rb:6:in `<main>'
```

こちらはエラーとなりました。

メソッド内で使いたいブロックを受け取っていないので、当然といえば当然な気もします。

でもブロックのあり・なしで処理を分けたいこともありそうな気がします。
調べてみると、メソッドにブロックが与えられているかを返す`block_given?`というメソッドがあったので、こちらを使ってみます。

```ruby:sample.rb
def sample(x, y)
  puts "yield:#{yield}" if block_given?
  x + y
end

puts "sample:#{sample(1, 2)}"
puts "="*10
puts "sample:#{sample(1, 2) { 3 }}"
```

```:実行結果
sample:3
==========
yield:3
sample:3
```

無事、ブロックの有無で条件分岐をすることができました。

ブロックといえば、`each`メソッドでレシーバーの要素を引数として受け取っているところをよく見ます。

```ruby
# こんなイメージ
array = ['a', 'b', 'c']

array.each do |str|
  puts str
end
```

というわけで、次はブロックに引数を渡してみたいと思います。

`yield`に引数を渡すことで、ブロック内でその引数を使うことができるようなので、やってみます。

```ruby:sample.rb
def sample(x, y)
  x + y + yield(1, 2, 3)
end

z = sample(1, 2) do |args1, args2, args3|
    puts "args: #{args1} #{args2} #{args3}"
    args1 + args2 + args3
end

puts "sample:#{z}"
```

```:実行結果
args: 1 2 3
sample:9
```

せっかく(？)なので、可変長引数に書き換えてみます。

```ruby:sample.rb
def sample(x, y)
  x + y + yield(1, 2, 3)
end

z = sample(1, 2) do |*args|
    puts "args:#{args}"
    args.sum
end

puts "sample:#{z}"
```

```:実行結果
args:[1, 2, 3]
sample:9
```

うん、いい感じです（自己満足）

さて、最後は少し逸れてしまいましたが、`yield`を好き放題いじったので、ここらで次に向かうこととします。

# Proc

`Proc`は、ブロックをオブジェクトとして扱うためのクラスです。

初めて聞いた時は、私はこんなのを想像していました。

```ruby:sample.rb
# エラーになります
proc = { puts "abc" }
```

```:実行結果
sample.rb:1: syntax error, unexpected string literal, expecting `do' or '{' or '('
proc = { puts "abc" }
sample.rb:1: syntax error, unexpected '}', expecting end-of-input
proc = { puts "abc" }
```

実際に`Proc`オブジェクトを作成するには`new`メソッドを、呼び出す時には`call`メソッドを使用します。

引数も渡すことができます。

```ruby:sample.rb
proc = Proc.new{ puts "abc" }
proc.call

proc = Proc.new{ |str| puts str }
proc.call("efg")
```

```:実行結果
abc
efg
```

メソッドにブロックとして`Proc`オブジェクトを渡したいときは、`&`を付けて、**最後の**引数にします。

```ruby:sample.rb
def sample(x)
  x + yield
end

proc = Proc.new{ 3 }

puts sample(1, &proc)
```

```:実行結果
4
```

ここは、あまり試したいことが思いつかなかったので、次へ進みます。

# lambda

`lambda`は、`Proc`オブジェクトを生成するためのメソッドです。

```ruby:sample.rb
lambda = ->(){ puts "abc"}
lambda.call

lambda = ->(str){ puts str }
lambda.call("efg")

puts lambda.class
```

```:実行結果
abc
efg
Proc
```

ぱっと見、`Proc.new`と書き方が違うだけにしか見えません。
試しに、クラスを確認してみたところ、Procであることもわかりました。

どこが違うのかは次のセクションへ。

# Proc.newとlambdaの違い

## 引数の扱い

`Proc.new`によりインスタンスを生成すると引数の数が一致していない場合は、余分な引数を無視します。

```ruby:sample.rb
proc = Proc.new{ |str| puts str }
proc.call("abc", "efg")
```

```:実行結果
abc
```

なお、引数の数が足りない場合は、`nil`を返します。

```ruby:sample.rb
proc = Proc.new{ |str| puts str }
puts proc.call.nil?
```

```:実行結果
true
```

一方で、`lambda`によりインスタンスを生成した場合は、引数の数が一致しないとエラーが発生します。

```ruby:sample.rb
lambda = ->(str){ puts str }
lambda.call("abc", "efg")
```

```:実行結果
sample.rb:4:in `block in <main>': wrong number of arguments (given 2, expected 1) (ArgumentError)
        from sample.rb:5:in `<main>'
```

## ジャンプ構文の挙動

`return`や`break`時の挙動が違うようなので、今回は`return`で試してみます。

`Proc.new`はブロック内で`return`すると、メソッドから脱出していることがわかります。

```ruby:sample.rb
def proc_method
  proc = Proc.new{ |str| return str }
  proc.call("abc")
  return "end"
end

puts proc_method
```

```:実行結果
abc
```

一方で、`lambda`はブロックを脱出するだけで、メソッドからは脱出していないことがわかります。

```ruby:sample.rb
def lambda_method
  lambda = ->(str){ return str }
  lambda.call("abc")
  return "end"
end

puts lambda_method
```

```:実行結果
end
```

# さいごに

 `yield`はあれこれ気になり色々と試しましたが、`Proc`、`lambda`は調べたことをコードに落とし込むのが中心となりました。

それでも、サンプルのコードを単に写すのではなく、リファレンスや書籍で説明されている挙動を確認するためのコードを自分で書くことで、理解は深まったと感じています。

さて、ここまで書いて一つ問題が発生しました。
「`yield`、`Proc`、`lambda`、こいつらはいったいどこで使ったらいいんだ？」
やはり使い所のイメージが湧きません…

こちらは今後の課題として、業務のコードを追うなどしながら探していきます。