Ruby の引数の種類をまとめてみた

[ブログ記事](https://secret-garden.hatenablog.com/entry/2022/03/11/210246)からの転載です。

普段何気なく書いている Ruby なんですが実はメソッドの引数の種類って思ったよりも多くてまとめてみました。
実際自分も Ruby の AST をいじっている時に引数の種類が多すぎて混乱したのでまとめておくと色々と役に立つかなーと。
と、軽い気持ちで書いてみたんですが結構なボリュームになってしまいました。
引数の名称とかは調べつつ独断と偏見で書いてあるのでそこら辺はご注意ください。

## 諸注意

* この記事は基本的に Ruby 3.1 を元にして動作確認しています
* 下にも書いてあるんですが `仮引数(parameter)` についての解説で `実引数(argument)` の話はあんまりでてきません
* 引数の名称は日本語に寄せていますが Ruby 界隈だと英語で書いてある事が多いかも
    * 調べる時は英語の方で調べてもらったほうが情報はでてくると思う


## 引数という言葉の意味とは

『引数』という言葉には厳密に言うと『仮引数』と『実引数』という2つの意味があります。

* 仮引数(parameter)：メソッドを定義する時に使う変数名の事
* 実引数(argument)：実際にメソッドに渡す（渡ってきた）値の事

```ruby
# メソッド定義時の a や b が仮引数(parameter)
def plus(a, b)
  a + b
end

# 実際にメソッドを渡す値が実引数(argument)
plus(1, 2)
```

この記事では『仮引数(parameter)』の種類について解説していきます。
なので特に記述がない場合は『引数＝仮引数(parameter)』と認識してください。


## Ruby の引数の種類

Ruby の引数の種類には大きく分けで4つあります。

* 位置引数 (positional parameter)
* キーワード引数 (keyword parameter)
* ブロック引数 (block parameter)
* 転送引数 (forwarding parameter)
    * この引数は Ruby 2.7 から追加された

ここから更に細かく分けると以下のように分類されます。

* 位置引数 (positional parameter)
    * 必須引数 (required parameter)
    * オプショナル引数 (optional parameter)
    * 残余引数 (rest parameter)
    * 後置引数 (post-required parameter)
    * 分割引数 (decomposed parameter)
* キーワード引数 (keyword parameter)
    * 必須引数 (required parameter)
    * オプショナル引数 (optional parameter)
    * 残余引数 (rest parameter)
* ブロック引数 (block parameter)
* 転送引数 (forwarding parameter)

位置引数とキーワード引数の項目で細かく分類されています。  
また、引数を定義できる順番は基本的には以下のようになります。  

```ruby
def test(
  必須引数 (位置引数),
  オプショナル引数 (位置引数),
  残余引数 (位置引数),
  後置引数 (位置引数),

  必須引数 (キーワード引数),
  オプショナル引数 (キーワード引数),
  残余引数 (キーワード引数),

  ブロック引数
)
end
```

分割引数は上の順番に含まれない特殊な引数なので注意してください。  
また転送引数に関しても書き方が特殊なので別途記述します。  
この記事ではこれらの引数について1つ1つ解説していきます。  
ちなみにこの記事では引数の名前を無理やり日本語に訳していますが Ruby 界隈だと英語読みで話すことが多いかも…？


## メソッドの引数情報を実行時に取得する

メソッドの引数情報は Ruby の実行時に `Method#parameters` で取得する事ができます。  

```ruby
def test(a, b = 1, *c, d, (e, f), g:, h: 2, **i, &j)
end

# method メソッドで Method オブジェクトを取得し、そこから parameters で引数情報を得る
pp method(:test).parameters
# => [[:req, :a],
#     [:opt, :b],
#     [:rest, :c],
#     [:req, :d],
#     [:req],
#     [:keyreq, :g],
#     [:key, :h],
#     [:keyrest, :i],
#     [:block, :j]]
```

| 記号 | 意味 |
| ---- | ---- |
| :req | 必須引数 (位置引数) や 後置引数 (位置引数) |
| :opt | オプショナル引数 (位置引数) |
| :rest | 残余引数 (位置引数) |
| :keyreq | 必須引数 (キーワード引数) |
| :key | オプショナル引数 (キーワード引数) |
| :keyrest | 残余引数 (キーワード引数) |
| :block | ブロック引数 |

ちなみに転送引数の場合は以下のような値が返ってきます。  

```ruby
def test(...)
end

pp method(:test).parameters
# => [[:rest, :*], [:keyrest, :**], [:block, :&]]
```

参照：https://docs.ruby-lang.org/ja/latest/method/Method/i/parameters.html


## 位置引数 (positional parameter)

* メソッドに値を渡した順番に依存する形で値を受け取る引数
    * 1つ目に渡した値は1つ目の引数で受け取る
* 位置引数には以下の定義方法がある
    * 必須引数 (required parameter)
    * オプショナル引数 (optional parameter)
    * 残余引数 (rest parameter)
    * 後置引数 (post-required parameter)
    * 分割引数 (decomposed parameter)
* メソッドに値を渡す時に `*` を付けると配列が展開された状態で値が渡される

```ruby
def test(a, b, c)
  { a: a, b: b, c: c }
end

# 配列の中身を展開して位置引数に値を渡す
# test(1, 2, 3) と同じ意味
pp test(*[1, 2, 3])
# => {:a=>1, :b=>2, :c=>3}
```


### 必須引数 (required parameter)

* 必ず値を渡す必要がある位置引数

```ruby
# 必ず2つの値を受け取るメソッド
def plus(a, b)
  a + b
end

# OK
# メソッドの引数：a = 1, b = 2
pp plus(1, 2)   # => 3
```

* 定義した必須引数の数と渡す値の数が一致していないとエラーになる

```ruby
def plus(a, b)
  a + b
end

# NG: 必要な引数が足りなくてエラーになる
# error: wrong number of arguments (given 1, expected 2) (ArgumentError)
pp plus(1)

# NG: 引数が多くてもエラーになる
# error: wrong number of arguments (given 3, expected 2) (ArgumentError)
pp plus(1, 2, 3)
```

### オプショナル引数 (optional parameter)

* デフォルト値を設定して定義する位置引数
* 必須引数とは違いメソッドに値を渡さなくてもエラーにならない
    * その場合はデフォルト値が変数に代入される

```ruby
# 値を渡さなかった場合は pi = 3.14159 になる
def circle_area(radius, pi = 3.14159)
  radius * radius * pi
end

# 値を渡すと pi = 2 となる
# メソッドの引数：radius = 2, pi = 3
pp circle_area(2, 3)   # => 12

# 値を渡さなかった場合は pi = 3.14159 となる
# メソッドの引数：radius = 2, pi = 3.14159
pp circle_area(2)   # => 12.56636
```

* デフォルト値は他のメソッドや他の引数を参照する事もできる

```ruby
def pi
  3.14
end
@value = 42

# 引数 a をデフォルト値として引数 b を定義する
def test(a, b = a, c = pi, d = @value)
  { a: a, b: b, c: c, d: d }
end

pp test(42)
# => {:a=>42, :b=>42, :c=>3.14, :d=>42}
```

* またオプショナル引数を必須引数よりも後に書くと次のような挙動になる
    * オプショナル引数ではなくて必須引数を優先して値を受け取る

```ruby
# 引数が1つの場合はオプショナル引数ではなくて必須引数で値を受け取る
def test(a = 1, b)
  { a: a, b: b }
end

# 引数が1つの場合は b = 5 になる
pp test(5)
# => {:a=>1, :b=>5}

# 引数が2つの場合は a = 6, b = 7 になる
pp test(6, 7)
# => {:a=>6, :b=>7}
```

* そして次のように必須引数の前後にオプショナル引数がある場合はエラーになるので注意する

```ruby
# error: syntax error, unexpected '=', expecting ')'
def test(a = 1, b, c = 2)
end
```

### 残余引数 (rest parameter)

* 必須引数やオプショナル引数（や後述の後置引数）以外で渡された値を全て受け取る位置引数
    * 渡された値は配列として受け取る
* `*` を付けて仮引数を定義する

```ruby
# 2つ目以降に渡された値を全てを args で受け取る
# args は渡された全ての値の配列になる
# 値がなければ空の配列になる
def sum(a, *args)
  return a + args.sum
end

# 1つ以上の任意の数の引数を渡すことができる

# メソッドの引数：a = 1, args = []
p sum(1)   # => 1

# メソッドの引数：a = 1, args = [2]
p sum(1, 2)   # => 3

# メソッドの引数：a = 1, args = [2, 3]
p sum(1, 2, 3)   # => 6
```

* また残余引数は仮引数名を省略する事もできる

```ruby
# 第一引数以外の位置引数は受け取らない
def first(a, *)
  a
end

pp first(1, 2, 3)
# => 1
```

* 更に Ruby 3.2 からは変数名を省略した場合でも別のメソッドに引数を渡せるようになる予定

```ruby
def foo(a, b, c)
  a + b + c
end

# Ruby 3.1 までは変数名を書いて他のメソッドに渡す必要があった
def hoge1(*args)
  # hoge1 で受け取った値をそのまま foo メソッドに渡す
  foo(*args)
end

# Ruby 3.2 からは * だけで他のメソッドに渡すことができるようになった
def hoge2(*)
  # hoge2 で受け取った値をそのまま foo メソッドに渡す
  foo(*)
end

pp hoge1(1, 2, 3)   # => 6
pp hoge2(1, 2, 3)   # => 6
```


### 後置引数 (post-required parameter)

* 残余引数以降に定義できる必須引数
* 残余引数と組み合わせて定義できる
* これを利用すると『残余引数の最後の値だけ』を受け取るようなこともできる

```ruby
# 一番最後に渡された引数を last で受け取る
def last(*args, last)
  last
end

# 複数の値を渡した中の最後の引数を返す
# メソッドの引数：args = [1, 2, 3], last = 4
pp last(1, 2, 3, 4)
# => 4

# 実引数が 1つだけの場合は last で値を受け取る
# メソッドの引数：args = [], last = 42
pp last(42)
# => 42
```

* 必須引数と組み合わせる事もできる

```ruby
# 前後の引数の値を受け取ることできる
def first_last(first, *middle, last)
  [first, last]
end

# メソッドの引数：fisrt = 1, middle = [2, 3], last = 4
pp first_last(1, 2, 3, 4)
# => [1, 4]
```

* また後置引数にはデフォルト値を設定する事はできない

```ruby
# syntax error, unexpected '=', expecting ')'
def test(*args, a = 1)
end
```


### 分割引数 (decomposed parameter)

* 配列を分割して受け取る位置引数
* 分割引数は必須引数と後置引数の部分で記述できる
    * `()` を付けて仮引数を定義する

```ruby
# 2つ目の値を配列の要素を分割して受け取る
def test(a, (b, c))
  { a: a, b: b, c: c }
end

# [2, 3] を b = 2, c = 3 に分割して受け取る
pp test(1, [2, 3])
# => {:a=>1, :b=>2, :c=>3}
```

* 分割引数は配列の要素数が定義した仮引数の数と違っていたり配列でなくても渡すことができる

```ruby
def test(a, (b, c))
  { a: a, b: b, c: c }
end

# 配列の要素数が多くても渡すことができる
pp test(1, [2, 3, 4])
# => {:a=>1, :b=>2, :c=>3}

# 逆に少ない場合は nil で埋められる
pp test(1, [2])
# => {:a=>1, :b=>2, :c=>nil}

# そもそも配列でなくても渡せる
pp test("hoge", "foo")
# => {:a=>"hoge", :b=>"foo", :c=>nil}

# ただし、3つ以上の値を渡すとエラーになる
# error: `test': wrong number of arguments (given 3, expected 2) (ArgumentError)
pp test(1, 2, 3)
```

* 残余引数の受け取り方は多重代入と同じような挙動になる
    * [詳細はこちら](https://docs.ruby-lang.org/ja/latest/doc/spec=2foperator.html#multiassign)

```ruby
# 配列の中身を分割して複数の変数に代入できる
foo, bar, baz = [1, 2, 3]
pp foo   # => 1
pp bar   # => 2
pp baz   # => 3

# * を付けると複数の値を配列で受け取る
piyo, *post = [1, 2, 3]
pp piyo   # => 1
pp post   # => [2, 3]

# 前後で分けて値を受け取る事もできる
first, *mid, last = [1, 2, 3, 4, 5]
pp first   # => 1
pp mid     # => [2, 3, 4]
pp last    # => 5
```

* また分割引数では残余引数も定義できる

```ruby
# () の中で残余引数も定義できる
def test(a, (b, *c))
  a + b + c.sum
end

# メソッドの引数：a = 1, b = 2, c = [3, 4]
pp test(1, [2, 3, 4])
# => 10

# こんな感じで複雑にネストして書くこともできる
def test2((a, (b, (c, *d)), e))
  a + b + c + d.sum + e
end

# メソッドの引数：a = 1, b = 2, c = 3, d = [4, 5], e = 6
pp test2([1, [2, [3, 4, 5]], 6])
# => 21
```

* ただし、分割引数ではオプショナル引数は定義できない

```ruby
# error: syntax error, unexpected '=', expecting ')'
def test(a, (b, c = 2))
end
```


## キーワード引数 (keyword parameter)

* 値を渡す時に名前を指定できる引数
    * `名前:` という形で仮引数を定義する
* 値を渡す時に名前を指定するので順番に依存せずに渡すことができる
    * 1つ目に渡した値が1つ目の引数で受け取るとは限らない
* キーワード引数には以下の定義方法がある
    * 必須引数 (required parameter)
    * オプショナル引数 (optional parameter)
    * 残余引数 (rest parameter)
* メソッドに値を渡す時に `**` を付けると `Hash` が展開された状態で値が渡される

```ruby
def test(a:, b:, c:)
  { a: a, b: b, c: c }
end

hash = { a: 1, b: 2, c: 3 }

# Hash の中身を展開してキーワード引数に値を渡す
# test(a: 1, b: 2, c: 3) と同じ意味
pp test(**hash)
# => {:a=>1, :b=>2, :c=>3}
```

* またキーワード引数は位置引数よりも後に定義する必要がある

```ruby
# OK
def test(a, b:)
end

# NG: syntax error, unexpected local variable or method
def test(b:, a)
end
```

* ちなみに `key => value` という形で渡すこともできる

```ruby
def test(a:)
  a
end

pp test(:a => 42)
# => 42
```


### 必須引数 (required parameter)

* 必ず値を渡す必要があるキーワード引数
* 定義した必須引数に値を渡さないとエラーになる

```ruby
# 名前: という形で引数を定義する
def circle_area(radius:, pi:)
  radius * radius * pi
end

# 名前: という形で引数を渡す
# メソッドの引数：radius = 4, pi = 3
pp circle_area(radius: 4, pi: 3)   # => 48

# error: `circle_area': missing keyword: :pi (ArgumentError)
pp circle_area(radius: 4)
```

* 名前を指定するので値を渡す順番に依存しない

```ruby
def circle_area(radius:, pi:)
  radius * radius * pi
end

# 引数の順番が異なっても渡すことができる
# メソッドの引数：radius = 2, pi = 3.14
pp circle_area(pi: 3.14, radius: 2)   # => 12.56
```


### オプショナル引数 (optional parameter)

* デフォルト値を設定して定義するキーワード引数
* 必須引数とは違いメソッドに値を渡さなくてもエラーにならない
    * その場合はデフォルト値が変数に代入される

```ruby
# 名前: デフォルト値、という形で引数を定義する
def circle_area(radius:, pi: 3.14159)
  radius * radius * pi
end

# デフォルト値のあるキーワード引数は省略できる
# メソッドの引数：radius = 2, pi = 3.14159
pp circle_area(radius: 2)   # => 28.27431

# 明示的に値を渡すこともできる
# メソッドの引数：radius = 4, pi = 3
pp circle_area(radius: 4, pi: 3)   # => 48
```

* オプショナル引数はキーワード引数のどの位置でも定義できる

```ruby
def test(a:, b: 2, c:, d: 4)
  { a: a, b: b, c: c, d: d }
end

pp test(c: 3, a: 1)
# => {:a=>1, :b=>2, :c=>4, :d=>3}
```


### 残余引数 (rest parameter)

* 必須引数やオプショナル引数以外で渡された値を全て受け取るキーワード引数
    * 受け取った値は `Hash` になる
* `**` を付けて仮引数を定義する

```ruby
# ** を付けて引数を定義する
# keyword 引数を Hash として受け取る
def create(**attributes)
  attributes
end

# メソッドの引数：attributes = {:name=>"Homu", :age=>14}
pp create(name: "Homu", age: 14)
# => {:name=>"Homu", :age=>14}
```

* また残余引数は仮引数を省略する事もできる

```ruby
# 第一引数以外のキーワード引数は受け取らない
def first(a:, **)
  a
end

pp first(a: 1, b: 2, c: 3)
# => 1
```

* 更に Ruby 3.2 からは変数名を省略した場合でも別のメソッドに引数を渡せるようになる予定

```ruby
def foo(a:, b:, c:)
  a + b + c
end

# Ruby 3.1 までは変数名を書いて他のメソッドに渡す必要があった
def hoge1(**kwd)
  # hoge1 で受け取った値をそのまま foo メソッドに渡す
  foo(**kwd)
end

# Ruby 3.2 からは * だけで他のメソッドに渡すことができるようになった
def hoge2(**)
  # hoge2 で受け取った値をそのまま foo メソッドに渡す
  foo(**)
end

pp hoge1(a:1, b: 2, c: 3)   # => 6
pp hoge2(a:1, b: 2, c: 3)   # => 6
```


### 位置引数にキーワード引数を渡す

* 位置引数でもキーワード引数を受け取れる
* その場合は `Hash` としてキーワード引数を受け取る

```ruby
# 位置引数でもキーワードを受け取る事ができる
def test(kwd)
  pp kwd
  # => {:a=>1, :b=>2, :c=>3}
end

# メソッドの引数：kwd = {:a=>1, :b=>2, :c=>3}
test(a: 1, b: 2, c: 3)
```

* 位置引数とキーワード引数がある場合はキーワード引数が優先される

```ruby
def test(a = 1, b: 2)
  [a, b]
end

pp test(b: 42)
# => [1, 42]


def test2(*args, **kwd)
  [args, kwd]
end

pp test2(a: 1, b: 2)
# => [[], {:a=>1, :b=>2}]
```

* また `**nil` と書くことで『明示的にキーワード引数を受け取らない』と定義することもできる
    * この書き方は Ruby 2.7 で追加された

```ruby
def test(a = 1, **)
end

# OK
test(b: 42)

def test2(a = 1, **nil)
end

# NG: no keywords accepted (ArgumentError)
test2(b: 42)
```

### キーワード引数の非互換な変更について

* Ruby 3.0 からキーワード引数の仕様が変更された
* Ruby 3.0 以前では次のように `Hash` オブジェクトをキーワード引数として渡すことが可能だった

```ruby
def test(a:, b:)
  a + b
end

hash = { a: 1, b: 2 }
p test(hash)
# => 3
```

* しかし Ruby 3.0 からはこれがエラーになるようになった

```ruby
def test(a:, b:)
  a + b
end

hash = { a: 1, b: 2 }

# eror: wrong number of arguments (given 1, expected 0; required keywords: a, b) (ArgumentError)
p test(hash)
```

* `Hash` オブジェクトをキーワード引数として渡したい場合は `**` を付けることで渡すことができる

```ruby
def test(a:, b:)
  a + b
end

hash = { a: 1, b: 2 }
p test(**hash)
# => 3
```

* キーワード引数の変更に関しては以下の記事を参照してください
* [Ruby 3.0における位置引数とキーワード引数の分離について](https://www.ruby-lang.org/ja/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/)


## ブロック引数 (block parameter)

* ブロックを受け取る事ができる引数
* 受け取った値は `Proc` オブジェクトになる
    * `Prco#call` でブロックの中の処理を呼び出すことができる

```ruby
# &名前 でブロック引数を受け取ることができる
def test(&block)
  pp block
  # => #<Proc:0x000056003b763b58 /path/to/test.rb:11>

  # call メソッドでブロックの処理を呼び出すことができる
  block.call(1, 2)
end

# メソッドの引数：block = #<Proc:0x00007f03114f59c8 /path/to/test.rb:11>
pp test { |a, b| a + b }
# => 3
```

* ブロック引数は必須引数ではないので引数がない場合は `nil` になる

```ruby
def test(&block)
  block
end

# ブロック引数がない場合は nil になる
pp test {}
# => nil
```

* またブロック引数は1つだけしか定義できない

```ruby
# ブロック引数は複数定義できない
# error: syntax error, unexpected ',', expecting ')'
def test(&block1, &block2)
end
```

* Ruby 3.1 からはブロック引数は仮引数を省略する事もできる
* その場合は `&` だけで他のメソッドにブロック引数を渡すことができる

```ruby
def hoge(&block)
  block.call
end

def foo(&)
  # & だけでブロック引数を他のメソッドに渡せる
  hoge(&)
end

pp foo { 42 }
# => 42
```

### ブロック引数の仮引数を省略する

* ブロック引数の仮引数を省略した場合に `yield` というキーワードでブロックの処理を呼び出すことができる

```ruby
def test
  # test(&block) で受け取って block.call(1, 2) しているのと同じ意味
  yield(1, 2)
end

pp test { |a, b| a + b }
# => 3
```

* `#block_given?` で『ブロック引数が渡されたかどうか』を判定して呼び出しを切り分ける事もできる

```ruby
def test
  if block_given?
    "ブロック引数がある"
  else
    "ブロック引数がない"
  end
end

pp test     # => "ブロック引数がない"
pp test {}  # => "ブロック引数がある"
```

### ブロック引数に `&` で値を渡す

* ブロック引数に値を渡す場合に `&` を付けて渡すことができる

```ruby
# ブロック引数に &:upcase を渡している
pp %w(homu mami mado).map(&:upcase)
# => ["HOMU", "MAMI", "MADO"]
```

* `&obj` でブロック引数に値を渡した時は `obj.to_proc` の値をブロック引数として受け取る

```ruby
def test(&block)
end

# :upcase.to_proc を block で受け取る
pp test(&:upcase)
```

* なので最初のコードは以下と同じ意味になる

```ruby
# :upcase.to_proc.call(it) は it.upcase と同じ事をしている
pp %w(homu mami mado).map { |it| :upcase.to_proc.call(it) }
# => ["HOMU", "MAMI", "MADO"]
```


## 転送引数 (forwarding parameter)

* 全ての引数を受け取って他のメソッドに渡すことができる引数
    * `...` という記述で引数を受け取る
* Ruby 2.7 から追加された新しい引数

```ruby
def circle_area(radius, pi: 3.14159)
  radius * radius * pi
end

# ... と書くことで全ての引数を受け取る事ができる
def debug_circle_area(...)

  # 受け取った引数を全て別の引数に渡すことができる
  pp(...)
  circle_area(...)
end

pp debug_circle_area(3, pi: 3.14)
# => 3
#    {:pi=>3.14}
#    28.26
```

* また Ruby 2.7.3 からは `test(name, ...)` のように位置引数と組み合わせる事もできる

```ruby
# name だけを位置引数として受け取る事ができる
def test(name, ...)
  pp name
  # => "homu"

  pp(name, ...)
  # => "homu"
  # => {:age=>14}

end

test("homu", age: 14)
```

* ただし、キーワード引数と組み合わせる事はできないので注意

```ruby
def test(name: , ...)
end
```


## ブロックの引数について

* ブロックの引数でも基本的にはメソッドの引数と同じように定義する事ができる

```ruby
block = proc { |a, b, c = 3, (d, e), f:, g: 7, **h, &i|
  [a, b, c, d, e, f, g, h, i.call]
}

pp block.call(1, 2, [4, 5], f: 6, h: 8, j: 9) { 10 }
# => [1, 2, 3, 4, 5, 6, 7, {:h=>8, :j=>9}, 10]
```

* ただし、以下のケースでメソッドの引数とは異なる点がある

### ブロックの引数はデフォルトで『分割引数』として値を受け取る

* ブロックの引数はデフォルトでは『分割引数』として受け取る
* なので引数が2つ以上あるのブロックに配列を渡すと次のような挙動になる

```ruby
# 配列ではない場合は普通に受け取る
pp proc { |a, b| [a, b] }.call 1, 2
# => [1, 2]

# a, b は分割引数として定義される
pp proc { |a, b| [a, b] }.call [1, 2]
# => [1, 2]

# 混じってる場合は配列として受け取る
pp proc { |a, b| [a, b] }.call [1, 2], 3
# => [1, 2], 3
```

* また仮引数が1つだけの場合でも末尾に `,` が付いていると分割引数になる

```ruby
# , がない場合は分割引数にならない
pp proc { |a| a }.call [1, 2]
# => [1, 2]

# , がある場合は分割引数になる
pp proc { |a,| a }.call [1, 2]
# => 1
```

* これは `Hash` を `each` する時などに注意する必要がある

```ruby
hash = { a: 1, b: 2, c: 3 }

# ブロックの引数が引数が一つの場合は配列で受け取る
hash.each { |it| pp it }
# => [:a, 1]
#    [:b, 2]
#    [:c, 3]

# ブロックの引数が2つの場合は配列を分割して受け取る
hash.each { |key, value| pp [key, value] }
# => [:a, 1]
#    [:b, 2]
#    [:c, 3]
```

### 仮引数よりも少ない数の値を渡す事ができる

* ブロックの引数は仮引数よりも少ない数の値を渡す事ができる

```ruby
# 仮引数は2つだが、実引数を1つだけ渡すことができる
# その場合、値を渡されなかった仮引数は nil になる
pp proc { |a, b| [a, b] }.call(1)
# => [1, nil]
```

* ただし `lambda` や `-> {}` でブロックが定義されている場合は引数の数が厳密にチェックされる
    * 必須引数になる

```ruby
# error: wrong number of arguments (given 1, expected 2) (ArgumentError)
pp lambda { |a, b| }.call(1)
```


### 転送引数には未対応

* ブロックの引数では転送引数を定義できない

```ruby
# syntax error, unexpected (..., expecting '|'
block = proc { |...|
  foo(...)
}
```


## 引数の定義の仕方まとめ

| 名前 | 書き方 | 説明 |
| -- | -- | --- |
| 必須引数 (位置引数) | `def test(a, b)` | 必ず必要な位置引数 |
| オプショナル引数 (位置引数) | `def test(a = 1, b = 2)` | 実引数がない場合はデフォルト値が代入される引数 |
| 残余引数 (位置引数) | `def test(*args)` | 複数の位置引数を受け取ることができる引数 |
| 後置引数 (位置引数) | `def test(*args, b)` | 残余引数以降にかける必須な引数 |
| 分割引数 (位置引数) | `def test((a, b))` | 配列を分割して受け取る事ができる引数 |
| 必須引数 (キーワード引数) | `def test(a:, b:)` | 名前を付けて渡せる引数 |
| オプショナル引数 (キーワード引数) | `def test(a: 1, b: 2)` | 実引数がない場合はデフォルト値が代入される引数 |
| 残余引数 (キーワード引数) | `def test(**kwd)` | 複数のキーワード引数を受け取ることができる引数 |
| ブロック引数 | `def test(&block)` | ブロック引数を受け取る特殊な引数 |
| 転送引数 | `def test(...)` | 引数を全て受け取り他のメソッドに渡すことができる特殊な引数 |


## 引数の渡し方まとめ

| 名前 | 渡し方 | 説明 |
| -- | -- | --- |
| 位置引数 | `test(1, 2)` | 仮引数の順番に依存して値を渡す |
| splat 引数 | `test(*[1, 2])` | 配列を展開して位置引数に値を渡す。 `test(1, 2)` と同じ意味 |
| キーワード引数 | `test(a: 1, b: 2)` や `test(:a => 1, :b => 2)` | 名前を指定して値を渡す |
| double splat 引数 | `test(**{ a: 1, b: 2 })` | `Hash` を展開してキーワード引数に値を渡す。 `test(a: 1, b: 2)` と同じ意味 |
| ブロック引数 | `test {  }` や `test do end` | `{}` や `do end` の中のコードを `Proc` オブジェクトとしてブロック引数に値を渡す |
| ブロック引数 (&渡し) | `test(&obj)` |  `obj.to_proc` の結果をブロック引数の値として渡す |

NOTE:  splat 引数や double splat 引数は日本語だとなんて言えばいいんですかね…


## まとめ

軽い気持ちでまとめようと思ったら思った以上に大ボリュームなまとめになってしまいました。  
もっとさくっとかけると思ってたんですが思ったよりも Ruby の引数って種類が多かったんですね、結構細かいところまで詰めて書いたつもりなんですがこれでも多分まだ全然足りない気がする…。
特にキーワード引数周りの細かい仕様とか（Ruby 3.0 の非互換な話とか）引数の転送の話とかキーワード引数の渡し方とか etc...。  
機会があればまた足りない部分は補足したいなあ。  


## 参照

* [norswap · Ruby Methods, Procs and Blocks](https://norswap.com/ruby-methods/)
* [クラス／メソッドの定義 (Ruby 3.0 リファレンスマニュアル)](https://docs.ruby-lang.org/ja/3.0/doc/spec=2fdef.html#method)
* [Ruby 3.0における位置引数とキーワード引数の分離について](https://www.ruby-lang.org/ja/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/)
* [Ruby 3: 引数をforwardする`…`記法が第2パラメータでも使えるようになった（翻訳）｜TechRacho by BPS株式会社](https://techracho.bpsinc.jp/hachi8833/2020_11_06/99916)
* [Defining methods - Ruby Reference](https://rubyreferences.github.io/rubyref/language/methods-def.html#arguments)
* [ Ruby adds support for forwarding arguments to a method, along with the leading arguments | Saeloun Blog ](https://blog.saeloun.com/2020/09/16/ruby-3.0-to-add-support-for-forwarding-arguments-along-with-lead-arguments)