なお、コード例には翻訳時に補助的なコメントを追加しています。
Ruby 2.7: ハッシュからキーワード引数への自動変換が非推奨に(翻訳)
Rubyのメソッド定義は極端なまでに柔軟です。それを端的に表す例がMarc-André Lafortuneのブログ記事にありましたので引用します。
class C
def hi(needed, needed2,
maybe1 = "42", maybe2 = maybe1.upcase,
*args,
named1: 'hello', named2: a_method(named1, needed2),
**options,
&block)
end
end
C.instance_method(:hi).parameters
# => [ [:req, :needed], [:req, :needed2],
# [:opt, :maybe1], [:opt, :maybe2],
# [:rest, :args],
# [:key, :named1], [:key, :named2],
# [:keyrest, :options],
# [:block, :block] ]
Ruby 2.7ではキーワード引数の設計の変更に踏み切ります。しかしその前にキーワード引数について理解しておきましょう。
🔗 そもそもキーワード引数とは?
キーワード引数はRuby 2.0で導入され、ちょうどオプション引数(つまりハッシュオブジェクト{ }
)と同じように扱われていました。キーワード引数を受け取るメソッドにオプション引数を渡すことも、逆にオプション引数を受け取るメソッドにキーワード引数を渡すことも可能です。
# Ruby 2.6まで
def a_method(k: 1) # キーワード引数を受け取るメソッド
puts "k: #{k}"
end
a_method
=> k: 1
a_method(k: 2) # キーワード引数を渡せる
=> k: 2
a_method({k: 2}) # オプション引数も渡せる
=> k: 2
def a_method(**kw) # オプション引数を受け取るメソッド
puts "kw, #{kw}"
end
a_method
=> kw, {}
a_method(k: 2) # キーワード引数も渡せる
=> kw, {:k=>2}
a_method({k: 2}) # オプション引数を渡せる
=> kw, {:k=>2}
「#14183 -- "Real" keyword argument」で指摘されているとおり、キーワード引数とオプション引数のどちらを渡してもよいという互換性は、実に多くのバグやエッジケースの温床となっていました。
RubyConf 2017では、Matzが「Ruby 3.0で『本物の』キーワード引数をお見せします」と公式にアナウンスしました(動画↓)。キーワード引数が通常の引数から完全に切り離されるのもそのひとつです。
この変更によって非互換性が生じます。Ruby 3.0でのキーワード引数の設計見直しに向けた準備のため、ハッシュをキーワード引数に自動変換する挙動はRuby 2.7で非推奨となります。
本記事では、この変更によって実際に影響を受ける可能性のあるコードと、Ruby 3.0をサポートするための移行方法について記します。
🔗 1. キーワード引数を受け取るメソッドにハッシュが渡される場合
# Ruby 2.7
def a_method(k: 1) # キーワード引数を受け取るメソッド
puts "k: #{k}"
end
a_method({k: 1}) # ハッシュを渡すとwarningが表示される
(irb):4: warning: The last argument is used as the keyword parameter
(irb):1: warning: for 'a_method' defined here
# => k: 1
# warningを回避し、かつRuby 3での互換性を確保するには
# double splat演算子を付けて渡す
a_method(**{k: 1})
# => k: 1
🔗 2. オプション引数とキーワード引数を両方受け取るメソッドに、渡した引数が足りない場合
# Ruby 2.7
def a_method(opts, **kw)
puts "opts, #{opts}"
puts "kw, #{kw}"
end
a_method(k: 1) # キーワード引数だけを渡すとwarningが表示される
(irb):5: warning: The keyword argument is passed as the last hash parameter
(irb):1: warning: for 'a_method' defined here
# => opts, {:k=>1}
kw, {}
# warningを回避し、かつRuby 3での互換性を確保するには
# 引数を{}で囲んでハッシュオブジェクトにする
a_method({k: 1}) # ハッシュオブジェクト { } ならOK
=> opts, {:k=>1}
=> kw, {}
# メソッド定義に**nilと書くことで
# メソッドがキーワード引数を受け取らないことを明示できる
def a_method(opts, **nil)
puts "opts, #{opts}"
end
a_method(k: 1)
#=> ArgumentError (no keywords accepted)
🔗 互換レイヤ
互換レイヤとして、オプション引数を受け取るメソッドにキーワード引数を渡すことは引き続き許容されます。
# Ruby 2.6と2.7
def a_method(opts={}) # オプションハッシュを受け取るメソッド
puts "opts, #{opts}"
end
a_method(k: 1) # これはOK
#=> opts, {:k=>1}
🔗 非シンボルがキーワード引数のキーとして使えるようになる
任意のキーワードをdouble splat演算子(**
)で受け取れるメソッドには、キーがシンボルでないキーワード引数を渡すことが許容されるようになります。
🔗 Ruby 2.6まで
def a_method(**kw) # double splatで任意のキーワードを受け取るメソッド
puts "kw, #{kw}"
end
a_method("k" => 1) # キーが非シンボルだとエラーになる
#=> ArgumentError: wrong number of arguments (given 1, expected 0)
from (pry):7:in 'a_method'
a_method(**{"k" => 1}) # { }で囲んでも**を付けてもダメ
#=> TypeError: wrong argument type String (expected Symbol)
from (pry):11:in `__pry__'
🔗 Ruby 2.7
def a_method(**kw) # double splatで任意のキーワードを受け取れる
puts "kw, #{kw}"
end
a_method("k" => 1) # 非シンボルキーを渡せる
=> kw, {"k"=>1}
🔗 まとめ
Ruby 2.7ではハッシュからキーワード引数への自動変換が非推奨になります。さまざまなシナリオからわかるように、アプリケーションでこの非推奨の用法から移行することをまず検討しましょう。今後も改善が続く部分につき、今後のRubyではさらに変更が行われるかもしれません。
おたより発掘
Ruby 2.7: ハッシュからキーワード引数への自動変換が非推奨に(翻訳)
山岡節だw
概要
元サイトの許諾を得て翻訳・公開いたします。
参考: Ruby 3.0における位置引数とキーワード引数の分離について