キーワード引数と通常の引数の分離

こんにちは!oda@エンジニア1年目です!

先日、Ruby2.7からRuby3.0での変更点について調べていました。

今回は、その中でもバージョンアップ時にエラーを引き起こす原因となる「キーワード引数と通常の引数の分離」をテーマにしたいと思います。

バージョンアップ時の対応

Ruby2.7でキーワード引数と通常の引数(具体的にはハッシュ)の間の自動変換が非推奨となり、警告が出るようになりました。

さらにRuby3.0では、キーワード引数と通常の引数が完全に分離され、警告ではなくエラーが出るようになりました。

上記変更は、キーワード引数として渡したいハッシュにdouble splat演算子(**)を足すことで、Ruby3.0でも同じようにアプリケーションを動かすことができます。

私も Ruby2.7へのバージョンアップ時には、キーワード引数として扱いたいハッシュに**を足すという対応をしました。

Ruby3.0への変更点を調べる中で、再び「キーワード引数と通常の引数の分離」に出会ったので、今回は、その背景まで見てみることにしました。

変更の背景

変更の背景は、「Ruby 3.0における位置引数とキーワード引数の分離について」(※)に詳しく書かれていました。

(以下、掲載のコードは※から引用しています。)

自動変換が非推奨・エラーに変わったのは、オプション引数とキーワード引数をどちらも受け取るメソッドでは混乱を招くケースがあるということが原因のようです。

オプション引数とは、デフォルト値を設定した引数のことを言います。

記事には、混乱を招くケースとして以下のコードが例示されていました。

def foo(x, **kwargs)
  p [x, kwargs]
end

def bar(x=1, **kwargs)
  p [x, kwargs]
end

foo({}) #=> [{}, {}]
bar({}) #=> [1, {}]

bar({}, **{}) #=> 期待は: [{}, {}]だが実際はl: [1, {}]

これは、第1引数の{}が自動的にキーワード引数(**kwargs)に変換され、第2引数の**{}が無視されていることによるものとのことでした。

たしかに、このコードを見た時「barの第1引数に{}に渡しているから、出力結果としては[{},{}]かな」と思いました。

記事の説明にあるとおり、混乱してしまいました。

今後、キーワード引数をどう使っていくか

キーワード引数と通常の引数が完全に分離されたことにより、キーワード引数を受け取りたいメソッドの定義は、いずれかの形にすべきとされています。

  • def foo(k: default)
  • def foo(k:)
  • def foo(**kwargs)

一方で、Ruby3.0でもキーワード引数を受け取らないメソッドを呼び出す時にキーワード引数を渡すことは、これまで同様にできるとのことです。

def foo(kwargs = {})
  kwargs
end

foo(k: 1) #=> {:k=>1}

こちらについては、禁止した場合のメリットが少ないことから、引き続き動作するようですが、新たなコードを書くときにはおすすめできないとされていました。

単に今までと同じように動くからよいではなく、今渡した引数がハッシュなのか、キーワード引数なのかをきちんと考えた上でコードを読み書きすることで、既存のコードもより良いコードにしていくことができそうです。

さいごに

今回、変更点の背景について調べることで、非推奨や禁止にはなっていないものの、新たにコードを書く時に使用しない方がよい書き方を知ることができました。

これはただ単に変更点とその対応方法について、調べるだけではわからなかったことです。

今回、学んだことは新たにコードを書く時や既存のコードを修正する時に活かしていきます!