Ruby2.5.xのパラメータの制約についてまとめてみた

こんにちは、hachi8833です。大江戸Ruby会議07で議論された「キーワード引数の現状と将来構想」をきっかけに、メソッド定義側の制約を中心にRubyのパラメータと引数について(主に自分のために)取り急ぎまとめました。誤りや理解不足がありましたら@hachi8833までお知らせください。 用語と書式 Rubyも含め、多くの言語では引数(argument)という用語とパラメータ(parameter)という用語は明確に区別されていません。そこで、以前翻訳した「Rubyのパラメータと引数の対応付けを理解する」に倣って、本記事に限って以下の用語と書式を用いることにしますのでご了承ください。これにより、一般にキーワード引数と呼ばれているものも定義側ではキーワードパラメータと表記します。 パラメータ: メソッド定義の変数リスト 引数: メソッド呼び出し側で渡す値リスト(変数やリテラル) キーバリュー: メソッド呼び出し側の引数におけるkey: :value形式の値(変数やリテラル) 必要ない限り、引数はすべて()で囲む書式で統一 なお、昔の一部の言語では本記事で言うメソッド定義側のパラメータを「仮引数」(dummy arguments)、メソッド呼び出し側の引数を「実引数」(actual arguments)と呼ぶ慣習があります。 環境はRuby 2.5.1を前提とします。また、記述はパラメータを中心にします。 パラメータの種類と略称 表記を簡略にするため、本記事では以下の略記を用います。これも本記事だけの表記であり、一般的なものではありません。 pN/pND 通常のパラメータ(a)かデフォルト値付きパラメータ(a=1) pVA 可変長配列パラメータ(*a): splatパラメータとも pK/pKD キーワードパラメータ(a:)かデフォルト値付きキーワードパラメータ(a: 1) pVH 可変長ハッシュパラメータ(**a): double splatパラメータとも pB ブロックパラメータ(&block) なお、本記事ではいわゆる「オプションハッシュパラメータ」は使い方の呼び名とみなし、パラメータの種類とは別ものと考えます(opt={}も**optもそう呼ばれることがあるため)。 1.のpNとpNDと、2.のpVAは通称「positionパラメータ」と呼ばれ、引数の順序をパラメータの順序と合わせる必要があります。 以上のパラメータは、デフォルト値付きとデフォルト値なしのものを類似とみなすと、以下のように5つのグループに分けられます(グループごとに色を変えてあります)。 パラメータをフルに使った極端なメソッド定義は以下のようになります(実際にここまですることはまずありませんが)。 def test(id, type=’normal’, *friends, name:, age: 0, **opts, &block) puts “name=#{name}, age=#{age}, type=#{type}” puts “id=#{id} friends=#{friends} opts=#{opts}” puts “block=#{&block}” end Rubyの構文上の制約 Ruby構文では、メソッド定義側のパラメータ順序の明確な制約は以下のようになります。思ったほどは多くありません。 バグかどうかという議論とは別に、「制約」という形で現状の動作を記述します。 ⚓制約(1) デフォルト値を持たないpNやpKについては引数を省略できない def foo(a);end foo() # 不可 def foo(a:);end foo() # 不可 ⚓制約(2) pNDはグループ化されていなければならない pNDとpNDの間に他のパラメータを置いて分断してはならない def foo(a=1, b, c=1) # 不可(pND, pN, pND) def foo(a, b=1, c=1, d) # 可(pNDの連続は許される) ⚓制約(3) pKやpKDは、positionパラメータ(pN、pND、pVA)より後でなければならない def foo(key:, a, b=1) # 不可(pK, pN, pD) def foo(a, b=1, key:) # 可(pN, pD, pK) ⚓制約(4) pVAはpNDより後に1つしか置けない ただし、pNDがなければpVAをpDの前に1つだけ置ける def foo(*a, *b) # 不可(pVA, pVA) def foo(*ary, a, b=1) # 不可(pVA, pN, pD) def foo(a, b=1, *ary) # 可(pN, pD, pVA) def foo(*a, b) # 可(pVA, pN) 補足: 制約(4)の挙動について このことから、pVAが前にあると、まず後ろのpNに引数を割り当て、残った引数が配列としてpVAに割り当てられると考えられます。 def foo(*a, b, c, d) # pVAが前にあると引数の値が後ろのpNから1つずつ割り当てられる puts “a: #{a}” puts “b: #{b}” puts “c: #{c}” puts “d: #{d}” end foo(1, 2, 3, 4, 5, 6, 7) # a: [1, 2, 3, 4] # b: 5 # c: 6 … Continue reading Ruby2.5.xのパラメータの制約についてまとめてみた