Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
274
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

Rubyのメソッドの引数受け渡しまとめ

※ 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つの要素からなる。

メソッド呼び出し.rb
レシーバ.メソッド(引数) {ブロック}
  1. レシーバ : オブジェクト
  2. メソッド : シンボル
  3. 引数 : オブジェクトのリスト
  4. ブロック : Procオブジェクト

レシーバがselfの場合は省略可能1、ブロックは&でも渡せる、等の細かいルールがあるが、基本はこの4つの要素から成り立っている。今回は「引数(parameters/arguments)」のみに注目して、検証していく。

呼び出し側

メソッドの「引数」はオブジェクトのリストだ。配列(Array)でも連想配列(Hash)でも無いただのオブジェクトのリストとして渡される。2

渡された引数の確認は下記メソッドで行った。引数のリストを配列にし、それを表示するようにしている。表示される内容をコメント# =>としてとして記載する。

確認.rb
def f(*args)
  p args
end

単純な呼び出し

,区切りで、オブジェクトを並べれば、その通りに渡される。

単純な呼び出し.rb
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メソッドがあるオブジェクト)の場合は、展開を行う。

配列展開呼び出し.rb
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リテラルの場合は{}を省略できる。

最後のHash呼び出し.rb
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

【エラー】最後のHash呼び出し_途中.rb
f(42, l: 6, r: 9, "answer") # [Error] syntax error

これらはあくまでも{}を省略したHashリテラルに過ぎない。しかし、この記法により、キーワード引数やオプション引数のような書き方を可能にするところに、Rubyの柔軟さが潜んでいる。

まとめ

このように、Rubyのメソッドの引数は単純化されている。他の言語のように、引数自体に名前がついたりすることは無く、書かれた順番通りにオブジェクトのリストが渡されるようになっている。これらの処理は受け取る側のメソッドの定義に影響されない。4 だが、受け取り側の書き方次第では、柔軟な可変長引数や、名前付き引数を実現できるようになっている。

受け取り側

メソッドはdefを用いて定義し、メソッド名の他に、仮引数を指定する。仮引数の書き方によって、呼び出し側から渡された引数のリストをどのように割り当てるかが変わってくる。

単純な受け取り

,区切りで、仮引数を並べれば、その通りに受け取れる。

単純な受け取り.rb
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]が代入される。この形式では引数の数が仮引数の数と必ず同じで無ければならない。

もし、数が一致しない場合は少なすぎても多すぎてもエラーになる。

【エラー】単純な受け取り_数の不一致.rb
f1(42, "answer") # [Error] ArgumentError
f1(42, "answer", [4, 8, 10], {l: 6, r: 9, op: "*", base: 13})
# [Error] ArgumentError

デフォルト式あり引数

仮引数に=をつけてデフォルトを設定することできる。

デフォルト式.rb
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]]

同じように順番に代入されるが、仮引数よりも引数の数が少ない場合は、足りない部分に関してはデフォルト式が使われる。このとき、引数の名前やデフォルト式の型は関係が無い。デフォルト式が無い仮引数は必須であり、省略はできないが、デフォルト式がある仮引数は必須では無くなる。

引数が、デフォルト式が無い通常の仮引数の数よりも少なかったり、渡される引数の方が多ければエラーになる。

【エラー】デフォルト式_数の不一致.rb
f2() # [Error] ArgumentError
f2(42, "answer", [4, 8, 10], {l: 6, r: 9, op: "*", base: 13})
# [Error] ArgumentError

デフォルト式有りの仮引数の後に、通常の引数を配置することもできる。その場合は、通常の仮引数が必ず埋まるように代入される。

デフォルト式_後からデフォルト式無し.rb
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}]

ただし、デフォルト式有りの仮引数の後の、通常の仮引数のそのまた後に、デフォルト式有りの仮引数は設置できない。つまり、デフォルト有りの仮引数は必ず連続して配置されていなければならない。

【エラー】デフォルト式_後からデフォルト式無しの後からデフォルト式有り.rb
def f2e(a = 0, b, c = [])
  p [a, b, c]
end
# [Error] syntax error

コラム - デフォルト式の評価はいつ行われるのか?

Rubyではデフォルトではなくデフォルトを指定する。数値や文字列等のリテラルも式であるため、通常のデフォルト値と考えても問題ないが、副作用を伴う式や、オブジェクトを生成する式の場合は注意が必要になる。

デフォルト式の評価は必要になったときに毎回行われる。デフォルト式が不要な時は一切評価されることは無い。しかし、デフォルト式が必要な時は毎回評価されるため、ArrayやString等は呼び出す度に異なるオブジェクトになる。副作用がある式をデフォルト式に設定する場合は注意が必要だ。5

デフォルト式_評価.rb
def fx(a = ->{print("check: ");0}[])
  p a
end
fx(1) # => 1
fx() # => check: 0
fx(1) # => 1
fx() # => check: 0

可変長引数

仮引数に*をつけることで余りを全て配列として受け取る事ができる。これを可変長引数という。

可変長引数.rb
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]]となる。*が付いた仮引数は必ず配列であり、余りの引数が無いなど空配列[]の場合もあり得る。数に制限は無くなるため、どんなに引数が多くても、エラーになることは無い。

デフォルト式有りの仮引数と後に置くことができる。デフォルト式がある仮引数が優先して代入され、*付きの仮引数にはその余りが入る。

可変長引数_デフォルト式有りの後.rb
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}]]

*付き仮引数の後に通常の引数があっても良い。その場合は、通常の引数が埋まるように読み取られる。

可変長引数_真ん中.rb
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}]

通常の仮引数がある場合は、その引数の数より少ないとエラーになる。逆に、数が多い場合はエラーになることはない。

【エラー】可変長引数_数の不一致.rb
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

Hashでまとめて受け取る.rb
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でない場合、**付きの仮引数に渡されることは無いため、多く渡されれば同じくエラーになる。

【エラー】Hashでまとめて受け取る_数の不一致.rb
def f4e(a, **b)
  p [a, b]
end
f4e() # [Error] ArgumentError
f4e(42, 'answer') #=> ArgumentError

Hashでまとめて受け取れるのは、キーがSymbolのアイテムのみである。キーがSymbol以外の場合は除外され、別のHashとして可変長引数などに渡される。

Hashでまとめて受け取る_シンボルではない.rb
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であれば、まとめに渡される。これは、後述するキーワード引数と組み合わせた場合も同様である。

キーワード引数

キーワード: デフォルト式の形式で、キーワード引数を設定できる。

キーワード引数.rb
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としてまとてめて渡しているだけで有り、{}があっても:キーワード => 式という形で渡しても違いは無い。

デフォルト式は省略できる。

キーワード引数_デフォルト式なし.rb
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]

ただし、デフォルト式を省略した場合は、そのキーワード引数は必須になる。もし、必須キーワードが渡されない場合は、エラーになる。

【エラー】キーワード引数_必要キーワード不足.rb
f52(42) # [Error] ArgumentError
f52(42, op: "*") # [Error] ArgumentError

デフォルト式のありなしに関わらず、キーワード引数の順番は関係無い。渡す側も同じ順番にする必要はない。

キーワード引数_順不同.rb
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のキーを引き受けることができる。

キーワード引数_余りをまとめる.rb
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}]

キーワードはローカル変数として扱うため、大文字から始められない(大文字から始めると定数扱いになるため)。

【エラー】キーワード引数_大文字から始まる.rb
def f5e(a, B: 0)
  p [a, B]
end
# [Error] formal argument cannot be a constant

キーワード引数自体はHashではない。{}をつけたり、:キーワード => 式と書き直すことはできない。

【エラー】キーワード引数_Hashのように.rb
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 のキーワード引数


  1. privateメソッドの場合は省略必須。 

  2. ただし、Cでの拡張ライブラリではArrayオブジェクトとして受け取る事も可能。 

  3. さらにその後方に、ブロックを渡すための&付き引数があっても良いが、別枠として考えるべきである。 

  4. 必要な引数の数が足りなかったり、必須のキーワード引数が渡されない場合はエラーとなるが、リストとして作成した後の動作になる。 

  5. 後述するキーワード引数のデフォルト式も同じ動作になる。 

  6. さらに後方に、ブロックを受け取る&付き仮引数があっても良いが、別枠として考えるべきである。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
274
Help us understand the problem. What are the problem?