Ruby 2.7 アドベントカレンダーの3日目の記事です。
キーワード引数
Ruby は 2.0 から次のような感じでキーワード引数を使用することができるようになりました。
def hoge(k: 123) p k end hoge() #=> 123 hoge(k: 456) #=> 456 hoge({k: 789}) #=> 789
2.7 では最後の形式で warning が出るようになりました。
a.rb:7: warning: The last argument is used as the keyword parameter a.rb:1: warning: for `hoge' defined here
キーワード引数は、最後の引数が Hash の場合に特別な扱いをするという感じだったのですが、それだと省略可能な位置引数(デフォルト値を持つ引数)がある場合に、予期しない挙動になるからということみたいです。
def hoge(h={}, k: 123) p [h, k] end hoge() #=> [{}, 123] hoge(k: 456) #=> [{}, 456] hoge({k: 789}) #=> [{}, 789] hoge({a: 789}) #=> unknown keyword: a (ArgumentError)
呼び出してる側は Hash オブジェクトを第一引数で渡してるだけなのに、キーワード引数として扱われてしまってるという…。
なお、シンボルじゃないキーが含まれてるとそれはキーワード引数としては扱われないんですが、
h = {"a"=>123, k: 789} hoge(h) #=> [{"a"=>123}, 789]
ひとつの Hash オブジェクトを渡したのに、勝手に位置引数とキーワード引数に分割されちゃうという罠。
ということで来たるべき 3.0 ではこの挙動を変更して、Hash をキーワード引数としては扱わないようにするらしく、その前に既存コードの修正を促すために warning を出すということのようです。
3.0 では、キーワード引数を持つメソッドを呼び出す際に、{ }
を省略すると Hash ではなくキーワード引数として扱われるようになるみたいです。
これに伴って、「復活したものや入りそうで入らなかったもの」で書いたように、2.6 までと若干の挙動の違いもあります。
なお、Hash をキーワード引数ではなく位置引数に渡したい場合は、2.7 では次のようにすれば良さそうです。
hoge({k: 789}, **{}) #=> [{:k=>789}, 123]
2.6 ではダメでした。
hoge({k: 789}, **{}) #=> [{}, 789]
キーワード引数を持たないメソッドでは、従来どおり { }
を省略してキーワード引数の形式で呼び出しても Hash として渡されます。
def hoge(h) p h end hoge({k: 123}) #=> {:k=>123} hoge(k: 123) #=> {:k=>123}
2.7 ではメソッドがキーワード引数を取らないことを明示することもできるようになりました。 この場合はキーワード引数の形式で呼び出すとエラーになります。
def hoge(h, **nil) p h end hoge({k: 123}) #=> {:k=>123} hoge(k: 123) #=> no keywords accepted (ArgumentError)
いろいろややこしいですが、2.7 は 3.0 前の最後のバージョン(たぶん)なので、移行期間ということでこのようになってるんでしょう。 互換性を重視する Ruby らしいですね。