引数をそのまま戻すメソッドを作る際の注意

# クイズ：引数をそのまま戻すメソッドを作りなさい。

与えた引数がそのまま戻ってくるメソッドってありますよね、`p`とか。そんなメソッドを作ってください。ただし引数は 1 つとは限りません。（キーワード引数やブロックは考えないことにします。）

つまり、以下のような結果が得られるメソッドです。

```ruby
return_args()     # => nil
return_args(1)    # => 1
return_args(1, 2) # => [1, 2]
```

クイズ形式にしてみましたが、私自身がそんなメソッドを作った際に引っかかってしまったので戒めの記事です。無駄に長くなってしまったので、結論だけ知りたい人は[#結論](#結論)へどうぞ。

記事中の実行結果はすべて以下のバージョンの Ruby を用いています。

```
ruby 3.1.1p18 (2022-02-18 revision 53f5fc4236) [x86_64-linux]
```


## 引数転送 `...` が使えるのでは？

Ruby 2.7 で`...`による引数の転送ができるようになりました。

```ruby
def foo(...)
  bar(...)
end
```

これを使えばいいのでは？

```ruby
def return_args(...)
  ...
end
```

```
return_args.rb:2: warning: ... at EOL, should be parenthesized?
return_args.rb:3: syntax error, unexpected `end'
```

ダメでした。単なる`...`は範囲演算子の`...`と判定されてしまうのでした。

引数転送の`...`にはカッコが必要らしいので、付けてみましょう。

```ruby
def return_args(...)
  return(...)
end
```

```
return_args.rb:2: syntax error, unexpected ')'
  return(...)
```

やっぱりダメでした。ちなみに`return`を入れてみましたが、入れても入れなくても結果は変わりません。`...`はあくまでも引数を別メソッドに転送するための記法であり、戻り値には使えないようです。[^1]

[^1]: そもそも現実的には「引数をただそのまま戻すメソッド」ではなく「引数を何か処理して、そしてそのまま戻すメソッド」を作るでしょうから、実際の引数にアクセスできない`...`はそういう意味でも使えません。（「何か処理して」の部分を他のメソッドにぶん投げれば使えますが。）

## Splat 演算子 * で受け取る

`...`はこうした場合には使えないことが分かったので、Splat 演算子で引数を受け取ることにしましょう。

```ruby
def return_args(*args)
  args
end
```

引数が 2 個以上であれば上のコードでいいのですが、引数が 0, 1 個の場合戻り値が配列になってしまってよろしくありません。

```ruby
return_args()     # => [] ←×
return_args(1)    # => [1] ←×
return_args(1, 2) # => [1, 2] ←○
```

### 戻り値にも Splat 演算子を使えばいいのでは？

Splat 演算子を使えば、配列を展開できます。

```ruby
a = [1, 2, 3]
foo *a # foo 1, 2, 3 と書いたのと同じ

a = [1]
foo *a # foo 1 と書いたのと同じ
```

ということは、こうなるのでは？

```ruby
a = [1, 2, 3]
return *a # return 1, 2, 3 と書いたのと同じ

a = [1]
return *a # return 1 と書いたのと同じ…？
```

というわけで、↓ で行ける？

```ruby
def return_args(*args)
  *args
end
```

```
return_args.rb:2: syntax error, unexpected '\n', expecting '='
  *args
```

む、よく分からないが構文的にダメらしい。`return`付けてみたらどうだろう？

```ruby
def return_args(*args)
  return *args
end
```

エラーが起きない！よし、これで行けた！

### と思いきや

```ruby
return_args()     # => [] ←×
return_args(1)    # => [1] ←×
return_args(1, 2) # => [1, 2] ←○
```

あれ、`*` 無しの場合と変わらない…

### 引数の数で処理を分ける

仕方ない、引数が 1 個以下の場合は別対応しましょう。

```ruby
def return_args(*args)
  args.size <= 1 ? args.first : *args
end
```

```
return_args.rb:2: syntax error, unexpected *
...args.size <= 1 ? args.first : *args
```

あれ、また SyntaxError だ。三項演算子だとダメなのか。`if`に替えよう。

```ruby
def return_args(*args)
  if args.size <= 1
    args.first
  else
    *args
  end
end
```

```
return_args.rb:5: syntax error, unexpected '\n', expecting '='
    *args
```

そうだった、さっきも同じミスしていたのに… `*args`だけではエラーになるので`return`を付けよう。

```ruby
def return_args(*args)
  if args.size <= 1
    return args.first
  else
    return *args
  end
end
```

できたー！…でも、ずいぶんとゴチャゴチャしてしまったなぁ…

### 先人の知恵を借りる

そういえば、`p`とか`pp`はどうやっているんだろう？

https://github.com/ruby/pp/blob/master/lib/pp.rb

```ruby
  def pp(*objs)
    # 略
    objs.size <= 1 ? objs.first : objs
  end
```

あれ、これだけでいいの？

[制御構造 (Ruby 3.1 リファレンスマニュアル) #return](https://docs.ruby-lang.org/ja/latest/doc/spec=2fcontrol.html#return)

> 式が2つ以上与えられた時には、それらを要素とする配列をメソッドの戻り値とします。

なるほど、戻り値が 2 個以上あるときは結局配列になっているのか。だから引数が 2 個以上の場合にはわざわざ Splat で展開しなくても、配列そのままで戻せばいいわけね。

そうか、Splat 演算子を使うという考えにとらわれていたから三項演算子がダメだったり`return`必須だったりしたけれど、使わなければこれだけシンプルに書けるのか。

# 結論

こうしましょう。

```ruby
def return_args(*args)
  args.size <= 1 ? args.first : args
end
```

`return *args`で行けると思い込んでいたのでテストもしておらず、某ライブラリを 1 年近く適切に動かないまま公開してしまっていました。すみません。