※ 2.7.0からキーワード引数を通常の引数から分離されました。**この記事はこの変更点について未反映です。**詳しくは2.7.0リリースノートを参考にしてください。
Rubyにおいて、メソッドへの引数がどのように渡され、どのように受け取っているのかをまとめた。動作確認はRuby 2.3.1で行っている。古いバージョンでは動作しない場合があるので注意して欲しい。それぞれの対応は下記を参考。
- Hashリテラルの
key: value
記法は1.9系から。 - デフォルト式あり引数や可変長引数の後に通常の引数を設置できるのは1.9系から。
- キーワード引数は2.0系から。ただし、2.0系ではデフォルト式の省略は不可。
- キーワード引数のデフォルト式省略は2.1系から。
- キーワード引数を通常の引数から分離は2.7系から。(現在、記事には未反映)
メソッド呼び出しの構成
Rubyのメソッド呼び出しは4つの要素からなる。
レシーバ.メソッド(引数) {ブロック}
- レシーバ : オブジェクト
- メソッド : シンボル
- 引数 : オブジェクトのリスト
- ブロック : Procオブジェクト
レシーバがself
の場合は省略可能1、ブロックは&
でも渡せる、等の細かいルールがあるが、基本はこの4つの要素から成り立っている。今回は「引数(parameters/arguments)」のみに注目して、検証していく。
呼び出し側
メソッドの「引数」はオブジェクトのリストだ。配列(Array)でも連想配列(Hash)でも無いただのオブジェクトのリストとして渡される。2
渡された引数の確認は下記メソッドで行った。引数のリストを配列にし、それを表示するようにしている。表示される内容をコメント# =>
としてとして記載する。
def f(*args)
p args
end
単純な呼び出し
,
区切りで、オブジェクトを並べれば、その通りに渡される。
f # => []
f() # => []
f(42) # => [42]
f(42, "answer") # => [42, "answer"]
f(42, "answer", [4, 8, 10]) # => [42, "answer", [4, 8, 10]]
f(42, "answer", [4, 8, 10], {l: 6, r: 9, op: "*", base: 13})
# => [42, "answer", [4, 8, 10], {:l=>6, :r=>9, :op=>"*", :base=>13}]
上の例は単純に、そのまま、1
や"answer"
や[4, 8, 10]
や{l: 6, r: 9, op: "*", base: 13}
等がリストとして渡される。ArrayやHashもオブジェクトの一つとして、単純に渡される。
なお、引数が無い、つまり、空のリストも許される。その場合は、()
を省略できる。
配列展開
引数の前に*
をつけると、そのオブジェクトがArrayまたはArrayへ変換できるオブジェクト(to_a
メソッドがあるオブジェクト)の場合は、展開を行う。
f(42, "answer", *[4, 8, 10], {l: 6, r: 9, op: "*", base: 13})
# => [42, "answer", 4, 8, 10, {:l=>6, :r=>9, :op=>"*", :base=>13}]
f(42, "answer", [4, 8, 10], *{l: 6, r: 9, op: "*", base: 13})
# => [42, "answer", [4, 8, 10], [:l, 6], [:r, 9], [:op, "*"], [:base, 13]]
f(*42, *"answer", *[4, 8, 10], *{l: 6, r: 9, op: "*", base: 13})
# => [42, "answer", 4, 8, 10, [:l, 6], [:r, 9], [:op, "*"], [:base, 13]]
f(42, *[], *{}, "answer") # => [42, "answer"]
*
はどのオブジェクトにもつけられるが、Hash等もto_a
の結果に応じて展開されることに注意すべきである。0..Float::INFINITY
のような無限リストを渡すと処理が無限ループになり、最終的にメモリ不足で落ちる。to_a
が無いオブジェクトの場合は、エラーにもならず、そのまま渡される。
配列が空またはto_a
の結果が空の場合は、その部分は何も渡されない、つまり、引数として無かったことにされる。
Hashの{}
省略
引数リストの最後がHashリテラルの場合は{}
を省略できる。
f(42, "answer", [4, 8, 10], l: 6, r: 9, op: "*", base: 13)
# => [42, "answer", [4, 8, 10], {:l=>6, :r=>9, :op=>"*", :base=>13}]
f(42, "answer", 1 => 6, 2 => 9, op: "*")
# => [42, "answer", {1=>6, 2=>9, :op=>"*"}]
Hashリテラルとしてのa: b
は:a => b
の糖衣構文にすぎず、全く同等として扱われる。そのため、記法が混じっている場合でも、一つのHashに入り、問題なく動作する。
省略できるのは一番最後のみである。下記のように途中にHashがある場合はエラーになる。そのため、必ず一つの最後にあるHashになるため、Hash自体が分割されることも無い。3
f(42, l: 6, r: 9, "answer") # [Error] syntax error
これらはあくまでも{}
を省略したHashリテラルに過ぎない。しかし、この記法により、キーワード引数やオプション引数のような書き方を可能にするところに、Rubyの柔軟さが潜んでいる。
まとめ
このように、Rubyのメソッドの引数は単純化されている。他の言語のように、引数自体に名前がついたりすることは無く、書かれた順番通りにオブジェクトのリストが渡されるようになっている。これらの処理は受け取る側のメソッドの定義に影響されない。4 だが、受け取り側の書き方次第では、柔軟な可変長引数や、名前付き引数を実現できるようになっている。
受け取り側
メソッドはdef
を用いて定義し、メソッド名の他に、仮引数を指定する。仮引数の書き方によって、呼び出し側から渡された引数のリストをどのように割り当てるかが変わってくる。
単純な受け取り
,
区切りで、仮引数を並べれば、その通りに受け取れる。
def f1(a, b, c)
p [a, b, c]
end
f1(42, "answer", [4, 8, 10]) # => [42, "answer", [4, 8, 10]]
上の例では三つの引数を取り、順番に、a
には42
が、b
には"answer"
が、c
には[4, 8, 10]
が代入される。この形式では引数の数が仮引数の数と必ず同じで無ければならない。
もし、数が一致しない場合は少なすぎても多すぎてもエラーになる。
f1(42, "answer") # [Error] ArgumentError
f1(42, "answer", [4, 8, 10], {l: 6, r: 9, op: "*", base: 13})
# [Error] ArgumentError
デフォルト式あり引数
仮引数に=
をつけてデフォルト式を設定することできる。
def f2(a, b = "", c = [])
p [a, b, c]
end
f2(42) # => [42, "", []]
f2(42, "answer") # => [42, "answer", []]
f2(42, "answer", [4, 8, 10]) # => [42, "answer", [4, 8, 10]]
同じように順番に代入されるが、仮引数よりも引数の数が少ない場合は、足りない部分に関してはデフォルト式が使われる。このとき、引数の名前やデフォルト式の型は関係が無い。デフォルト式が無い仮引数は必須であり、省略はできないが、デフォルト式がある仮引数は必須では無くなる。
引数が、デフォルト式が無い通常の仮引数の数よりも少なかったり、渡される引数の方が多ければエラーになる。
f2() # [Error] ArgumentError
f2(42, "answer", [4, 8, 10], {l: 6, r: 9, op: "*", base: 13})
# [Error] ArgumentError
デフォルト式有りの仮引数の後に、通常の引数を配置することもできる。その場合は、通常の仮引数が必ず埋まるように代入される。
def f2p(a, b = "", c = [], d)
p [a, b, c, d]
end
f2p(42, "answer") # => [42, "", [], "answer"]
f2p(42, "answer", [4, 8, 10]) # => [42, "answer", [], [4, 8, 10]]
f2p(42, "answer", [4, 8, 10], {l: 6, r: 9, op: "*", base: 13})
# => [42, "answer", [4, 8, 10], {:l=>6, :r=>9, :op=>"*", :base=>13}]
ただし、デフォルト式有りの仮引数の後の、通常の仮引数のそのまた後に、デフォルト式有りの仮引数は設置できない。つまり、デフォルト有りの仮引数は必ず連続して配置されていなければならない。
def f2e(a = 0, b, c = [])
p [a, b, c]
end
# [Error] syntax error
コラム - デフォルト式の評価はいつ行われるのか?
Rubyではデフォルト値ではなくデフォルト式を指定する。数値や文字列等のリテラルも式であるため、通常のデフォルト値と考えても問題ないが、副作用を伴う式や、オブジェクトを生成する式の場合は注意が必要になる。
デフォルト式の評価は必要になったときに毎回行われる。デフォルト式が不要な時は一切評価されることは無い。しかし、デフォルト式が必要な時は毎回評価されるため、ArrayやString等は呼び出す度に異なるオブジェクトになる。副作用がある式をデフォルト式に設定する場合は注意が必要だ。5
def fx(a = ->{print("check: ");0}[])
p a
end
fx(1) # => 1
fx() # => check: 0
fx(1) # => 1
fx() # => check: 0
可変長引数
仮引数に*
をつけることで余りを全て配列として受け取る事ができる。これを可変長引数という。
def f3(a, *b)
p [a, b]
end
f3(42) # => [42, []]
f3(42, "answer") # => [42, ["answer"]]
f3(42, "answer", [4, 8, 10]) # => [42, ["answer", [4, 8, 10]]]
b
について、1番目は[]
、2番目は["answer"]
、3番目は["answer", [4, 8, 10]]
となる。*
が付いた仮引数は必ず配列であり、余りの引数が無いなど空配列[]
の場合もあり得る。数に制限は無くなるため、どんなに引数が多くても、エラーになることは無い。
デフォルト式有りの仮引数と後に置くことができる。デフォルト式がある仮引数が優先して代入され、*
付きの仮引数にはその余りが入る。
def f23(a, b = "", *c)
p [a, b, c]
end
f23(42) # => [42, "", []]
f23(42, "answer") # => [42, "answer", []]
f23(42, "answer", [4, 8, 10]) # => [42, "answer", [[4, 8, 10]]]
f23(42, "answer", [4, 8, 10], {l: 6, r: 9, op: "*", base: 13})
# => [42, "answer", [[4, 8, 10], {:l=>6, :r=>9, :op=>"*", :base=>13}]]
*
付き仮引数の後に通常の引数があっても良い。その場合は、通常の引数が埋まるように読み取られる。
def f3c(a, *b, c)
p [a, b, c]
end
f3c(42, "answer") # => [42, [], "answer"]
f3c(42, "answer", [4, 8, 10]) # => [42, ["answer"], [4, 8, 10]]
f3c(42, "answer", [4, 8, 10], {l: 6, r: 9, op: "*", base: 13})
# => [42, ["answer", [4, 8, 10]], {:l=>6, :r=>9, :op=>"*", :base=>13}]
通常の仮引数がある場合は、その引数の数より少ないとエラーになる。逆に、数が多い場合はエラーになることはない。
f3() # [Error] ArgumentError
*
付きの仮引数は一つしか設置できない。また、デフォルト式有りの仮引数が含まれる場合は、デフォルト式有りの仮引数の後にしか設置できない。
def f3e1(*a, *b)
p [a, b]
end
# [Error] syntax error
def f3e2(a, *b, c = [])
p [a, b, c]
end
# [Error] syntax error
最後のHash
これから示す方法で最後にHashを受け取る事を明示すると、引数の一番最後がHash時のみ特別な処理がされる。
Hashでまとめて受け取り
**
とするとHashとして受け取ることができる。これは最後にただ一つ書くことができる。6
def f4(a, *b, **c)
p [a, b, c]
end
f4(42) # => [42, [], {}]
f4(42, 'answer') # => [42, ["answer"], {}]
f4(42, 'answer', [4, 8, 10]) # => [42, ["answer", [4, 8, 10]], {}]
f4(42, 'answer', [4, 8, 10], {l: 6, r: 9, op: "*", base: 13})
# => [42, ["answer", [4, 8, 10]], {:l=>6, :r=>9, :op=>"*", :base=>13}]
f4(42, {l: 6, r: 9, op: "*", base: 13})
# => [42, [], {:l=>6, :r=>9, :op=>"*", :base=>13}]
f4({l: 6, r: 9, op: "*", base: 13})
# => [{:l=>6, :r=>9, :op=>"*", :base=>13}, [], {}]
渡された引数の最後がHashの場合のみ、**
が付いた仮引数に渡され、*
のまとまりからは除外される。デフォルト式がある場合も同様に対象とならない。もし、最後がHashでない場合や、必須の引数として渡し終わっている場合は、空のHash{}
を渡していると見なされる。
処理としてはデフォルト式がない通常の仮引数が優先して埋められる。渡された引数の数と通常の仮引数の数が同じ場合は、通常の仮引数のみが埋められ、**
付き仮引数は空のHash{}
になる。渡された引数の方が多く、最後の引数がHashである場合のみ、最後の引数は**
付きの仮引数に割り当てれる。余った分がデフォルト式あり引数や可変長引数に渡される。
通常の引数よりも少なかったり、受け取れる引数より多く渡されたりした場合は、エラーになる。なお、最後がHashでない場合、**
付きの仮引数に渡されることは無いため、多く渡されれば同じくエラーになる。
def f4e(a, **b)
p [a, b]
end
f4e() # [Error] ArgumentError
f4e(42, 'answer') #=> ArgumentError
Hashでまとめて受け取れるのは、キーがSymbolのアイテムのみである。キーがSymbol以外の場合は除外され、別のHashとして可変長引数などに渡される。
f4(42, 'answer', {l: 6, r: 9, "op" => '*', 0 => 13, :"a b" => nil})
# => [42, ["answer", {"op"=>"*", 0=>13}], {:l=>6, :r=>9, :"a b"=>nil}]
:"a b" => nil
のようにキー: 値
の形式で渡せない場合でも、キーがSymbolであれば、まとめに渡される。これは、後述するキーワード引数と組み合わせた場合も同様である。
キーワード引数
キーワード: デフォルト式
の形式で、キーワード引数を設定できる。
def f51(a, *b, l: 0, r: 1)
p [a, b, l, r]
end
f51(42) # => [42, [], 0, 1]
f51(42, 'answer', [4, 8, 10]) # => [42, ["answer", [4, 8, 10]], 0, 1]
f51(42, 'answer', [4, 8, 10], {l: 6, r: 9})
# => [42, ["answer", [4, 8, 10]], 6, 9]
f51(42, l: 6, r: 9) # => [42, [], 6, 9]
f51(42, :l => 6, :r => 9) # => [42, [], 6, 9]
f51(42, {:l => 6, :r => 9}) # => [42, [], 6, 9]
キーワード引数をつけると、通常の仮引数のように、キーワードの名前が仮引数の名前となって使用できるようになる。渡されたHashの対応するキー(キーは必ずSymbol)の値が仮引数に代入される。
キーワード引数の処理は**
付き引数と同じで、最後がHashで無ければならない。呼び出し側で説明したとおり、最後がHashの場合は{}
を省略できる。そのため、あたかもキーワードとして引数を渡してかのような記述ができる。しかし、結局はHashとしてまとてめて渡しているだけで有り、{}
があっても:キーワード => 式
という形で渡しても違いは無い。
デフォルト式は省略できる。
def f52(a, *b, l:, r:, op: "+", base: 2)
p [a, b, l, r, op, base]
end
f52(42, l: 6, r: 9) # => [42, [], 6, 9, "+", 2]
f52(42, 'answer', [4, 8, 10], l: 6, r: 9, op: "*", base: 13)
# => [42, ["answer", [4, 8, 10]], 6, 9, "*", 13]
ただし、デフォルト式を省略した場合は、そのキーワード引数は必須になる。もし、必須キーワードが渡されない場合は、エラーになる。
f52(42) # [Error] ArgumentError
f52(42, op: "*") # [Error] ArgumentError
デフォルト式のありなしに関わらず、キーワード引数の順番は関係無い。渡す側も同じ順番にする必要はない。
def f53(a, *b, op: "+", base: 2, l:, r:)
p [a, b, l, r, op, base]
end
f53(42, l: 6, r: 9) # => [42, [], 6, 9, "+", 2]
f53(42, 'answer', [4, 8, 10], l: 6, r: 9, op: "*", base: 13)
# => [42, ["answer", [4, 8, 10]], 6, 9, "*", 13]
キーワード引数のさらに後に**
付き引数をつけることで、余ったHashのキーを引き受けることができる。
def f54(a, *b, l: 0, r: 1, **c)
p [a, b, l, r, c]
end
f54(42, l: 6, r: 9) # => [42, [], 6, 9, {}]
f54(42, 'answer', [4, 8, 10], l: 6, r: 9, op: "*", base: 13)
# => [42, ["answer", [4, 8, 10]], 6, 9, {:op=>"*", :base=>13}]
キーワードはローカル変数として扱うため、大文字から始められない(大文字から始めると定数扱いになるため)。
def f5e(a, B: 0)
p [a, B]
end
# [Error] formal argument cannot be a constant
キーワード引数自体はHashではない。{}
をつけたり、:キーワード => 式
と書き直すことはできない。
def f5e1(a, :b => 0)
p [a, b]
end
# [Error] syntax error
def f5e2(a, {b: 0})
p [a, b]
end
# [Error] syntax error
まとめ
呼び出し側でも書いたが、Rubyでの引数はただのリストに過ぎない。受け取り側はそのリストをそれぞれ割り当てているだけになっている。しかし、配列へまとめることや、最後のHashを特別なものと見なす記法によって、呼び出し側と受け取り側が対比するようになっている。他の言語に見られるような名前付き引数を実現していることは注目に値する。
それぞれに対比する書き方であれば、その通り渡されるのがわかるであろう。しかし、実際は対比する必要は全くない。名前付き引数の所をただのHashとして渡すこともできるし、名前付き引数のような部分をただの仮引数の一つとして受け取る事もできる。数さえあっていれば、配列の展開や集約はいかようにも渡したり、受け取ったりできる。
ブロックについて
最初にこのまとめでは除外すると述べたが、さらに最後の最後に&
をつけることでブロックを引数のように渡したり受け取ったりすることができる。これは全ての引数のさらに後、本当の最後のみ可能であり、特殊な引数と見なすこともできるが、呼び出し側でdo ... end
用いたり、受け取り側でyield
使ったりする場合と区別せずに用いることができるようになっている。
参考文献:
クラス/メソッドの定義 (Ruby 2.3.0)#メソッド定義
Rubyist Magazine - Ruby 2.0.0 のキーワード引数