コンテンツにスキップ

メソッドの引数

これはメソッドの引数と呼び出しの形式的な仕様です。

メソッド定義のコンポーネント

メソッド定義は次のものから構成されます。

  • 必須もしくはオプションの位置指定の引数がいくつか
  • 任意でスプラット展開指定された引数
  • 必須もしくはオプションの位置指定の引数がいくつか
  • 任意で二重スプラット展開指定された引数

例をあげます。

def foo(
  # 位置指定の引数
  x, y, z = 1,
  # スプラット展開指定された引数:
  *args,
  # 名前付き引数:
  a, b, c = 2,
  # 二重スプラット展開指定された引数:
  **options
)
end

これらを指定するかどうかは、それぞれ任意です。なのでスプラット、二重スプラット、キーワード引数、位置指定の引数を持たないようなメソッドを定義することができます。

メソッド呼び出しのコンポーネント

メソッド呼び出しは次のパーツを持ちます。

foo(
  # 位置指定の引数:
  1, 2,
  # 名前付き引数:
  a: 1, b: 2
)

くわえてメソッドはスプラット展開 (*) と二重スプラット展開 (**) を持つことができます。スプラット展開は Tuple を位置指定の引数に展開して、二重スプラット展開は NamedTuple を名前付き引数に展開します。複数のスプラット展開と二重スプラット展開をすることができます。

どのように渡された引数がメソッドの仮引数にマッチするのか

メソッドを呼び出す際、次のアルゴリズムが渡された引数をメソッドの仮引数にマッチさせるのに使われます。

  • まず、位置指定の引数が対応する位置指定の仮引数にマッチします。このようにマッチした引数の数は、少なくともデフォルト値のない位置指定の引数の数である必要があります。もしスプラット展開指定された引数に名前があれば(名前がない場合については後に説明します)、よりたくさんの位置指定の引数を渡すことができて、それらはタプルとしてその引数にキャプチャされます。位置指定の引数はスプラット展開指定されたメソッドの仮引数のあとではマッチしません。
  • 続けて、名前付き引数がその名前によって、対応する仮引数にマッチします。このマッチする引数はスプラット展開指定された引数の前でも後でも構いません。もしその引数が位置指定の引数として既にマッチしていた場合、エラーとなります。
  • その他の名前付き引数は二重スプラット指定の引数があればそちらに NamedTuple として配置されます。二重スプラット指定の引数がなければエラーとなります。

スプラット展開指定された引数に名前がついていないときは、位置指定の引数をさらに渡すことはできませんが、以降の引数は名前付き引数として渡さなければいけないことを意味します。例をあげます。

# ちょうど1つの位置指定の引数と、y という名前の名前付き引数を渡さなければいけないメソッド
def foo(x, *, y)
end

foo 1        # Error, missing argument: y
foo 1, 2     # Error: wrong number of arguments (given 2, expected 1)
foo 1, y: 10 # OK

しかし、スプラット展開指定された引数が名前を持っていた場合でも、以降の引数は名前付き引数として渡さなければいけないことは変わりません。

# 1つ以上の位置指定の引数と、y という名前の名前付き引数を渡さなければいけないメソッド
def foo(x, *args, y)
end

foo 1             # Error, missing argument: y
foo 1, 2          # Error: missing argument; y
foo 1, 2, 3       # Error: missing argument: y
foo 1, y: 10      # OK
foo 1, 2, 3, y: 4 # OK

最初にアスタリスクを置くことで、すべての引数を名前付き引数として渡さなければいけないようなメソッドを作ることもできます。

# x と y という2つの名前付き引数を渡さなければいけないメソッド
def foo(*, x, y)
end

foo            # Error: missing arguments: x, y
foo x: 1       # Error: missing argument: y
foo x: 1, y: 2 # OK

アスタリスク以降の引数はデフォルト値を持つこともできます。つまり、その引数は名前付き引数として渡さなければいけないが必須ではない、オプショナルな名前付き引数になる、ということです。

# どちらも名前付き引数として渡す必要があるが、x は必須で y は必須ではないメソッド
def foo(*, x, y = 2)
end

foo            # Error: missing argument: x
foo x: 1       # OK, y is 2
foo x: 1, y: 3 # OK, y is 3

スプラット展開指定された引数のあとの引数は必ず名前付き引数として渡されるため、その名前によるメソッドのオーバーロードが可能です。

def foo(*, x)
  puts "x と共に渡された: #{x}"
end

def foo(*, y)
  puts "y と共に渡された: #{y}"
end

foo x: 1 # => x と共に渡された: 1
foo y: 2 # => y と共に渡された: 2

位置指定の引数も名前付き引数として渡すことができます。

def foo(x, *, y)
end

foo 1, y: 2    # OK
foo y: 2, x: 3 # OK

外部名

メソッドの仮引数には外部名を指定できます。外部名は名前付き引数として渡す際に使う名前で、内部名はメソッド定義の本体で使う名前となります。

def foo(external_name internal_name)
  # ここで internal_name を使えます
end

foo external_name: 1

これには2つのユースケースがあります。

1つはキーワードを名前付き引数として使いたい場合です。

def plan(begin begin_time, end end_time)
  puts "#{begin_time}#{end_time} の間で予定しています"
end

plan begin: Time.now, end: 2.days.from_now

2つ目のユースケースはメソッド本体での可読性を向上させるため、です。

def increment(value, by)
  # OK、ですが読んでいて違和感があります
  value + by
end

def increment(value, by amount)
  # こちらの方が良いでしょう
  value + amount
end