Rubyメソッドの引数まとめ

Ruby

検証環境のrubyのバージョンは2.7.1です。

通常の引数

通常の引数は以下の通りです。
メソッドで引数名を定義し、メソッド呼び出し時に値を設定します。

def message(greet, name)
  "#{greet} #{name}."
end

message("Hey", "Bob")
=> "Hey Bob."

デフォルト引数(引数名 = )

デフォルト引数(デフォルト値付き引数)は引数名 = デフォルト値と記述する引数です。
デフォルト引数を利用することで引数のデフォルト値が設定できます。

『メソッド呼び出し時の引数の数』が『メソッドで定義された引数の数』より少ない場合、デフォルト引数はデフォルト値を利用します。

def message(greet, name = "there")
  "#{greet} #{name}."
end

### 引数の値が使われる
message("Hey", "Bob")
=> "Hey Bob."

### nameはデフォルト値が利用される
message("Hey")
=> "Hey there."

### 通常の引数の値が設定されていないのでエラー
message()
# ArgumentError (wrong number of arguments (given 0, expected 1..2))

参考: デフォルト引数よりもキーワード引数が望ましい

デフォルト引数は引数の順序に依存します。そのためデフォルト引数よりも、引数の順序に依存しないキーワード引数(後述)のほうが一般的には好ましいとされています。1 2

デフォルト引数

def message(greet = "Hi", name = "there")
  "#{greet} #{name}."
end

message("Hey", "Bob")
=> "Hey Bob."

### 引数の順序を変えると結果も変わる
message("Bob", "Hey")
=> "Bob Hey."

キーワード引数

def message(greet: "Hi", name: "there")
  "#{greet} #{name}."
end

message(greet: "Hey", name: "Bob")
=> "Hey Bob."

### 引数の順序を変えても結果は変わらない
message(name: "Bob", greet: "Hey")
=> "Hey Bob"

『複数のデフォルト引数 + 通常の引数』の場合、デフォルト引数どうしは隣接していないといけない

通常の引数と複数のデフォルト引数を利用する場合、デフォルト引数どうしは引数の順序において連続していなければいけません。3

以下に、いくつかパターンを紹介します。

### OK: func(デフォルト引数, デフォルト引数, 通常の引数)パターン
def message(greet = "Hi", name = "there", destination)
  "#{greet} #{name}. Let's go to #{destination}."
end

message("Hey", "Bob", "McDonald's")
=> "Hey Bob. Let's go to McDonald's."

message("Hey", "McDonald's")
=> "Hey there. Let's go to McDonald's."

message("McDonald's")
=> "Hi there. Let's go to McDonald's."
### OK: func(通常の引数, デフォルト引数, デフォルト引数)パターン
def message(greet, name = "there", destination = "a restaurant")
  "#{greet} #{name}. Let's go to #{destination}."
end

message("Hey")
=> "Hey there. Let's go to a restaurant."

message("Hey", "Bob")
=> "Hey Bob. Let's go to a restaurant."

message("Hey", "Bob", "McDonald's")
=> "Hey Bob. Let's go to McDonald's."
### NG: func(デフォルト引数, 通常の引数, デフォルト引数)パターン
def message(greet = Hi, name, destination = "a restaurant")
  "#{greet} #{name}. Let's go to #{destination}."
end
### OK: func(通常の引数, デフォルト引数, デフォルト引数, 通常の引数)パターン
def message(greet, name = "there", destination = "a restaurant", purpose)
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

message("Hi", "eat hamburgers")
=> "Hi there. Let's go to a restaurant to eat hamburgers."

message("Hi", "Bob", "eat hamburgers")
=> "Hi Bob. Let's go to a restaurant to eat hamburgers."

message("Hi", "Bob", "McDonald's", "eat hamburgers")
=> "Hi Bob. Let's go to McDonald's to eat hamburgers."

参考: func(hoge, options = {}) という書き方について

コードリーディングをしているとfunc(hoge, options = {})のような記述をよくみかけるかもしれません。
options = {}はキーワード引数(後述)がないRuby 2.0以前の時代に、keyとvalueのペアをメソッドへ渡したいときによく利用された記述方法です。4

キーワード引数でも同等のことができるため、現在はoptions = {}よりもキーワード引数を利用した記述方法が推奨されています。1

hoge("fuga", piyo: "hogera")

### options = {} を利用した記述
def hoge(arg, options = {})
  piyo = options[:piyo] || "hogehoge"
  p arg # => "fuga"
  p piyo # => "hogera"
end

### キーワード引数を利用した記述
def hoge(arg, piyo: "hogehoge")
  p arg # => "fuga"
  p piyo # => "hogera"
end

キーワード引数(引数名:)

キーワード引数は引数名: 値もしくは引数名:と記述する引数です。

引数名: 値は引数名のデフォルト値です。呼び出し元で当該引数の値が設定されていない場合に利用されます。
引数名:と記述した場合、呼び出し元で当該引数の値の設定が必須になります。

def message(greet: "Hi", name:)
  "#{greet} #{name}."
end

message(greet: "Hey", name: "Bob")
=> "Hey Bob."

### greetはデフォルト値が利用される
message(name: "Bob")
=> "Hi Bob."

### nameの値は必須なのでエラーになる
message(greet: "Hey")
# ArgumentError (missing keyword: :name)

メソッドの引数がキーワード引数で構成されている場合、呼び出し元の引数の順序は任意

キーワード引数では引数名と値が対応しているため、引数の順序は任意です。

def message(greet: "Hi", name: "there")
  "#{greet} #{name}."
end

message(greet: "Hey", name: "Bob")
=> "Hey Bob."

### 引数の順序を変えても結果は変わらない
message(name: "Bob", greet: "Hey")
=> "Hey Bob"

『通常の引数 + キーワード引数』の場合、キーワード引数の位置は通常の引数よりも後にしないといけない

通常の引数とキーワード引数を利用する場合、キーワード引数は引数の順序において通常の引数よりも後でなければいけません。5

また、呼び出し元の引数の順序も『通常の引数 → キーワード引数』にする必要があります。

### OK: func(通常の引数, キーワード引数)パターン
def message(greet, name: "there")
  "#{greet} #{name}."
end

message("Hey")
=> "Hey there."

message("Hey", name: "Bob")
=> "Hey Bob."

### 『通常の引数 → キーワード引数』ではないのでエラーになる
message(name: "Bob", "Hey")
# SyntaxError ((irb):6: syntax error, unexpected ')', expecting =>)
### NG: func(キーワード引数, 通常の引数)パターン
def message(greet: "Hello", name)
  "#{greet} #{name}."
end

キーワード引数にハッシュを渡すときは『**ハッシュ』の形式にする

Rubyではキーワード引数とハッシュを自動変換してくれます。
そのためキーワード引数にハッシュを渡しても問題なく動くのですがUsing the last argument as keyword parameters is deprecated; maybe ** should be added to the callという警告がでます。

def message(greet: "Hi", name: "there")
  "#{greet} #{name}."
end

### キーワード引数ではなくハッシュを利用しているため警告が出る
params = { greet: "Hey", name: "Bob" }
message(params)
(irb):11: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
(irb):7: warning: The called method message is defined here
=> "Hey Bob."

### 『**ハッシュ』の形式にすることで警告が出なくなる
params = { greet: "Hey", name: "Bob" }
message(**params)
=> "Hey Bob."

追記: ruby 3系の場合、『**ハッシュ』の形式にしないと実行できない

ruby 2.7.1では警告こそでるものの『**ハッシュ』の形式にしなくてもメソッドの実行はできました。

$ docker run --rm -it ruby:2.7.1 irb

### メソッドの定義
def message(greet, name, **invitations)
  destination = invitations[:destination] || "a restaurant"
  purpose = invitations[:purpose] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

### 実行できるが警告される
params = { destination: "McDonald's", purpose: "eat hamburgers" }
message("Hey", "Bob", params)
# warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
# warning: The called method `message' is defined here
=> "Hey Bob. Let's go to McDonald's to eat hamburgers."

ruby 3系の環境では実行できなくなります。

$ docker run --rm -it ruby:3.0-alpine irb

### メソッドの定義
def message(greet, name, **invitations)
  destination = invitations[:destination] || "a restaurant"
  purpose = invitations[:purpose] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

### 実行できない
params = { destination: "McDonald's", purpose: "eat hamburgers" }
message("Hey", "Bob", params)
# in `message': wrong number of arguments (given 3, expected 2) (ArgumentError)

当該メソッドの仕様変更の詳細はSeparation of positional and keyword arguments in Ruby 3.0をご覧になってください。

可変長引数(*引数名)

可変長引数は*引数名と記述する引数です。仮引数の数よりも実引数の数が多い場合、余った実引数を配列として受け取ります。

def message(greet, name, *invitation)
  destination = invitation[0] || "a restaurant"
  purpose = invitation[1] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

### invitationには["McDonald's", "eat hamburgers"]がセットされる
message("Hi", "Bob", "McDonald's", "eat hamburgers")
=> "Hi Bob. Let's go to McDonald's to eat hamburgers."

### invitationには["McDonald's"]がセットされる
message("Hi", "Bob", "McDonald's")
=> "Hi Bob. Let's go to McDonald's to eat something."

### invitationには[]がセットされる
message("Hi", "Bob")
=> "Hi Bob. Let's go to a restaurant to eat something."

引数順は『通常の引数 → デフォルト引数 → 可変長引数 → キーワード引数』にしないといけない

引数順は『通常の引数 → デフォルト引数 → 可変長引数 → キーワード引数』でないといけません。6

可変長引数は余った実引数を配列として受け取ります。
ですので、可変長引数の前に引数(通常の引数もしくはデフォルト引数)が設定されている場合は、当該引数に実引数が設定されたときのみ可変長引数に配列がセットされるので注意してください。

以下に、いくつかパターンを紹介します。

### OK: func(デフォルト引数, デフォルト引数, 可変長引数)パターン
# ただし、引数の数によって意図した挙動にならないので注意が必要
def message(greet = "Hi", name = "there", *invitation)
  destination = invitation[0] || "a restaurant"
  purpose = invitation[1] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

### invitationには["McDonald's", "eat hamburgers"]がセットされる
message("Hey", "Bob", "McDonald's", "eat hamburgers")
=> "Hey Bob. Let's go to McDonald's to eat hamburgers."

### invitationには[]がセットされる
# 実引数はデフォルト引数にセットされるため、意図した結果にならない
# (本当はinvitationに["McDonald's", "eat hamburgers"]がセットされてほしかった)
message("McDonald's", "eat hamburgers")
=> "McDonald's eat hamburgers. Let's go to a restaurant to eat something."

### OK: func(通常の引数, デフォルト引数, 可変長引数)パターン
# ただし、引数の数によって意図した挙動にならないので注意が必要
def message(greet, name = "there", *invitation)
  destination = invitation[0] || "a restaurant"
  purpose = invitation[1] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end


### invitationには["McDonald's", "eat hamburgers"]がセットされる
message("Hey", "Bob", "McDonald's", "eat hamburgers")
=> "Hey Bob. Let's go to McDonald's to eat hamburgers."

### invitationには[]がセットされる
message("Hey", "Bob")
=> "Hey Bob. Let's go to a restaurant to eat something."

### invitationには[]がセットされる
message("Hey")
=> "Hey there. Let's go to a restaurant to eat something."

### invitationには["eat hamburgers"]がセットされる
# "Hey"が通常の引数、"McDonald's"がデフォルト引数セットされるため、意図した結果にならない
# (本当はinvitationに["McDonald's", "eat hamburgers"]がセットされてほしかった)
message("Hey", "McDonald's", "eat hamburgers")
=> "Hey McDonald's. Let's go to eat hamburgers to eat something."
### OK: func(通常の引数, 可変長引数, キーワード引数)パターン
def message(greet, *who_where, purpose: "eat something")
  name = who_where[0]
  destination = who_where[1]
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

### who_whereには["Bob", "McDonald's"]がセットされる
message("Hey", "Bob", "McDonald's")
=> "Hey Bob. Let's go to McDonald's to eat something."
### NG: func(キーワード引数, キーワード引数, 可変長引数)パターン
def message(greet: "Hi", name: "there", *invitation)
  destination = invitation[0] || "a restaurant"
  purpose = invitation[1] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end
### NG: func(デフォルト引数, 可変長引数, デフォルト引数)パターン
def message(greet: "Hi", *who_where, purpose = "eat something")
  name = who_where[0]
  destination = who_where[1]
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end
### NG: func(デフォルト引数, 通常の引数, 可変長引数)パターン
def message(greet = "Hi", name, *invitation)
  destination = invitation[0] || "a restaurant"
  purpose = invitation[1] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

オプション引数(**引数名)

オプション引数(オプションハッシュ)は**引数名と記述する引数です。メソッド呼び出し元から渡されたキーワード引数をハッシュとして受け取ります。

def message(greet, name, **invitations)
  destination = invitations[:destination] || "a restaurant"
  purpose = invitations[:purpose] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

### invitationsに{:destination=>"McDonald's", :purpose=>"eat hamburgers"}がセットされる
message("Hey", "Bob", destination: "McDonald's", purpose: "eat hamburgers")
=> "Hey Bob. Let's go to McDonald's to eat hamburgers."

キーワード引数にハッシュを渡すときは『**ハッシュ』の形式にする

オプション引数はキーワード引数を受け取ってハッシュに変換します。
ですので、呼び出し元でキーワード引数ではなくハッシュを利用する場合は『**ハッシュ』の形式にする必要があります。

def message(greet, name, **invitations)
  destination = invitations[:destination] || "a restaurant"
  purpose = invitations[:purpose] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

### 実行できるが警告される
params = { destination: "McDonald's", purpose: "eat hamburgers" }
message("Hey", "Bob", params)
# warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
# warning: The called method `message' is defined here
=> "Hey Bob. Let's go to McDonald's to eat hamburgers."

### OK
message("Hey", "Bob", **params)
=> "Hey Bob. Let's go to McDonald's to eat hamburgers."

オプション引数は最後でないといけない

オプション引数は引数の最後に1つだけ設定できます。3

以下に、いくつかパターンを紹介します。

### OK: func(デフォルト引数, 通常の引数, オプション引数)パターン
def message(greet = "Hi", name, **invitations)
  destination = invitations[:destination] || "a restaurant"
  purpose = invitations[:purpose] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

message("Hey", "Bob", destination: "McDonald's", purpose: "eat hamburgers")
=> "Hey Bob. Let's go to McDonald's to eat hamburgers."

message("Hey", "Bob")
=> "Hey Bob. Let's go to a restaurant to eat something."

message("Bob")
=> "Hi Bob. Let's go to a restaurant to eat something."
### OK: func(キーワード引数, キーワード引数, オプション引数)パターン
def message(greet:, name: "there", **invitations)
  destination = invitations[:destination] || "a restaurant"
  purpose = invitations[:purpose] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

message(greet: "Hey", name: "Bob", destination: "McDonald's", purpose: "eat hamburgers")
=> "Hey Bob. Let's go to McDonald's to eat hamburgers."

message(greet: "Hey", name: "Bob")
=> "Hey Bob. Let's go to a restaurant to eat something."

message(greet: "Hey")
=> "Hey there. Let's go to a restaurant to eat something."

### OK: func(通常の引数, キーワード引数, オプション引数)パターン
def message(greet, name: "there", **invitations)
  destination = invitations[:destination] || "a restaurant"
  purpose = invitations[:purpose] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

message("Hey", name: "Bob", destination: "McDonald's", purpose: "eat hamburgers")
=> "Hey Bob. Let's go to McDonald's to eat hamburgers."

message("Hey", destination: "McDonald's", purpose: "eat hamburgers")
=> "Hey there. Let's go to McDonald's to eat hamburgers."
### NG: func(通常の引数, オプション引数, デフォルト引数)パターン
def message(greet, **invitations, name = "there")
  destination = invitations[:destination] || "a restaurant"
  purpose = invitations[:purpose] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

### NG: func(オプション引数, 通常の引数, 通常の引数)のパターン
def message(**invitations, greet, name)
  destination = invitations[:destination] || "a restaurant"
  purpose = invitations[:purpose] || "eat something"
  "#{greet} #{name}. Let's go to #{destination} to #{purpose}."
end

ブロック引数(&ブロック名)

ブロック引数は&ブロック名と記述する引数です。ブロック処理を引数として受け取り、block.callでブロック処理を実行します。

def message(&block)
  print "Hey Bob. "
  block.call
  print " to eat hamburgers."
end

message do
  print "Let's go to McDonald's"
end
# Hey Bob. Let's go to McDonald's to eat hamburgers.

ブロック引数は最後でないといけない

ブロック引数は引数の最後に1つだけ設定できます。7
オプション引数も最後に設定しないといけない引数ですが、オプション引数と組み合わせる場合であっても、ブロック引数が最後に設定すべき引数となります。

以下に、いくつかパターンを紹介します。

### OK: func(通常の引数, ブロック引数)パターン
def message(destination, &block)
  print "Hey Bob. "
  block.call(destination)
  print " to eat hamburgers."
end

message_proc = -> (destination) {
  print "Let's go to #{destination}"
}

message("McDonald's", &message_proc)
# Hey Bob. Let's go to McDonald's to eat hamburgers

### OK: func(オプション引数, ブロック引数)パターン
def message(**invitations, &block)
  destination = invitations[:destination] || "a restaurant"
  purpose = invitations[:purpose] || "eat something"
  print "Hey Bob. "
  block.call(destination, purpose)
end

message_proc = -> (destination, purpose) {
  print "Let's go to #{destination} to #{purpose}."
}


message(destination: "McDonald's", purpose: "eat hamburgers", &message_proc)
# Hey Bob. Let's go to McDonald's to eat hamburgers

message(destination: "McDonald's", &message_proc)
# Hey Bob. Let's go to McDonald's to eat something.

message(&message_proc)
# Hey Bob. Let's go to a restaurant to eat something.
### NG: func(ブロック引数, 通常の引数)パターン
def message(&block, destination)
  print "Hey Bob. "
  block.call(destination)
  print " to eat hamburgers."
end
# SyntaxError: unexpected ',', expecting ')'

まとめ

Rubyメソッドの引数まとめ
  • デフォルト引数は引数の順序に依存するため、キーワード引数のほうが好ましい
  • キーワード引数の位置は通常の引数の後にしなければいけない
  • 可変長引数は余った実引数を配列として受け取る。余りがなければ配列に値はセットされない。
  • 可変長引数を含むメソッドの引数順は『通常の引数 → デフォルト引数 → 可変長引数 → キーワード引数』にする
  • オプション引数は引数の最後に設定でき、キーワード引数をハッシュとして受け取る
  • ブロック引数はブロックを受け取る
  • ブロック引数は引数の最後に1つだけ設定できる
  • キーワード引数とハッシュは自動変換されるが、自動変換に依存しない実装にする

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考