🍣

Ruby の位置引数とキーワード引数で混乱したのでメモ

2023/02/07に公開

tl;dr

  • 位置引数とキーワード引数が厳密に区別され : の有無で決定されるので注意する
def param_by_position(param)
#                          ^
end

def param_by_keywords(param:)
#                          ^
end
  • 2.7.5 と 3.2.0 ではエラータイミングも違うので注意する

起きたこと

  • 位置引数とキーワード引数が混在するメソッドで、全てキーワード引数として指定しようとしたら Hash と解釈されて思ってもないエラーになって悩んだ

分かったこと

  1. 位置引数とキーワード引数は区別され、位置引数の仮引数名を呼び出し時に指定しても、位置引数をキーワードを付きで指定することはできない
    • C# だと呼び出し時にキーワードを付与することができるのでここが誤解の元だった
  2. 位置引数とキーワード引数と区別して(デフォルトパラメータの有無から)必要な引数の個数が管理される
      1. の仕様とあわせて、位置引数の場所に仮引数の名前を付けてキーワード呼び出しをすると、位置引数の個数があわなくなる
  3. 2.7.5 と 3.2.0 では解釈の違いがあるパターンがある
    • (a, b: nil) という引数を取るメソッドに (a:1,b:1) を渡したとき
      • 2.7.5 では呼び出し時の引数を全体として Hash と解釈して、位置引数に渡す
        • なので呼び出し時にはエラーにならず、その後の a の利用時に型があわないエラーを引き起こす
      • 3.2.0 では呼び出し時の引数を Hash に解釈せず、個数が合わないエラーとなる
        • ので、呼び出しエラーとなりエラー原因箇所を特定しやすそう

コード

以下のコードで確認

t.rb
def test1(a:,b:2)
  print(a)
  print("\n")
  print(b)
  print("\n"
  return
end
def test2(a,b:2)
  print(a)
  print("\n")
  print(b)
  print("\n"
  return
end

test1(1,2)
test1(a:1,b:2)
test1(1,b:2)
test2(1,2)
test2(a:1,b:2)
test2(1,b:2)

2.7.5 の動作

t.rb(main):019:0> test1(1,2)
Traceback (most recent call last):
        5: from /usr/local/bin/irb:23:in `<main>'
        4: from /usr/local/bin/irb:23:in `load'
        3: from /usr/local/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
        2: from t.rb:18
        1: from t.rb:1:in `test1'
ArgumentError (wrong number of arguments (given 2, expected 0; required keyword: a))
t.rb(main):020:0> test1(a:1, b:2)
1
2
=> nil
t.rb(main):021:0> test1(1, b:2)
Traceback (most recent call last):
        5: from /usr/local/bin/irb:23:in `<main>'
        4: from /usr/local/bin/irb:23:in `load'
        3: from /usr/local/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
        2: from t.rb:21
        1: from t.rb:1:in `test1'
ArgumentError (wrong number of arguments (given 1, expected 0; required keyword: a))
t.rb(main):022:0> test2(1,2)
Traceback (most recent call last):
        6: from /usr/local/bin/irb:23:in `<main>'
        5: from /usr/local/bin/irb:23:in `load'
        4: from /usr/local/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
        3: from t.rb:21
        2: from t.rb:22:in `rescue in irb_binding'
        1: from t.rb:9:in `test2'
ArgumentError (wrong number of arguments (given 2, expected 1))
t.rb(main):023:0> test2(a:1, b:2)
{:a=>1, :b=>2}
2
=> nil
t.rb(main):024:0> test2(1, b:2)
1
2
=> nil

3.2.0 の動作

t.rb(main):017:0> test1(1,2)
t.rb:1:in `test1': wrong number of arguments (given 2, expected 0; required keyword: a) (ArgumentError)
	from t.rb:17:in `<top (required)>'
	from /usr/local/lib/ruby/gems/3.2.0/gems/irb-1.6.2/exe/irb:11:in `<top (required)>'
	from /usr/local/bin/irb:25:in `load'
	from /usr/local/bin/irb:25:in `<main>'
t.rb(main):018:0> test1(a:1,b:2)
1
2
=> nil
t.rb(main):019:0> test1(1,b:2)
t.rb:1:in `test1': wrong number of arguments (given 1, expected 0; required keyword: a) (ArgumentError)
	from t.rb:19:in `<top (required)>'
	from /usr/local/lib/ruby/gems/3.2.0/gems/irb-1.6.2/exe/irb:11:in `<top (required)>'
	from /usr/local/bin/irb:25:in `load'
	from /usr/local/bin/irb:25:in `<main>'
t.rb(main):020:0> test2(1,2)
t.rb:9:in `test2': wrong number of arguments (given 2, expected 1) (ArgumentError)
	from t.rb:20:in `<top (required)>'
	from /usr/local/lib/ruby/gems/3.2.0/gems/irb-1.6.2/exe/irb:11:in `<top (required)>'
	from /usr/local/bin/irb:25:in `load'
	from /usr/local/bin/irb:25:in `<main>'
t.rb(main):021:0> test2(a:1,b:2)
t.rb:9:in `test2': wrong number of arguments (given 0, expected 1) (ArgumentError)
	from t.rb:21:in `<top (required)>'
	from /usr/local/lib/ruby/gems/3.2.0/gems/irb-1.6.2/exe/irb:11:in `<top (required)>'
	from /usr/local/bin/irb:25:in `load'
	from /usr/local/bin/irb:25:in `<main>'
t.rb(main):022:0> test2(1,b:2)
1
2
=> nil

まとめ

  • : の有無に注意する
  • 動作検証コードを書くときにバージョンをあわせるよう注意する
GitHubで編集を提案

Discussion