Scala 2.8 における 名前付き引数とデフォルト引数
(Named and Default Arguments in Scala 2.8)
1 はじめに
同じ型の複数のパラメータをとるメソッドは、コンパイル時に検出することのできない間違いの元になります。同じ型の 2 つの引数を交換してもエラーにはなりませんが、予期しない結果をもたらす可能性があります。 この問題は、名前付き引数を使うことで手際よく避けることができます。 さらに、名前付き引数にすれば、多数の引数を持つメソッド呼び出しの可読性が増します。
このドキュメントで論じられる、言語の 2 番目のフィーチャーであるデフォルト引数は、一般に名前付き引数とは独立したことです。 しかし 2 つのフィーチャーを併用すれば、有用性が実際に増します。たとえば、メソッドのパラメータリストの終わりにデフォルト値をもつパラメータを置く必要がなくなります。そのようなわけで、2 つのフィーチャーを一緒に導入します。
2 名前付き引数 (Named arguments)
Scala 2.8 では、変数の代入と同じ構文を使う名前付きのスタイルで、メソッド引数を指定できます。:
def f[T](a: Int, b: T)
f(b = getT(), a = getInt())
引数式は呼び出す場所順で評価されるので、上の例では、getT() は getInt() の前に実行されます。名前付きと位置引数の混成は、位置引数部が引数リストの前方にある形なら許されます。
f(0, b = "1") // 有効
f(b = "1", a = 0) // 有効
// f(b = "1", 0) // 無効、名前付き引数の後に位置引数あり
// f(0, a = 1) // 無効、パラメータ 'a' の 2 度指定
もし引数式が "x = expr" の形で、x がメソッドのパラメータ名でないなら、その引数は、ある変数 x への代入式として扱われます。つまり引数型は Unit です。ですから次の例は期待通り、依然動作します。
def twice(op: => Unit) = { op; op }
var x = 1
twice(x = x + 1)
もし、式 "x = expr" が名前付き引数(パラメータ名 x)あるいは、代入(スコープ中の変数 x)の両方に解釈できるなら、エラーです。 もし式が丸括弧あるいは中括弧の付加的なセットで囲まれていれば、決して名前付き引数として扱われません。 同様に、適用引数が( f{ arg } のような)ブロック式なら、arg は決して名前付き引数として扱われません。
def twice(op: => Unit) = { op; op }
var op = 1
// twice(op = op + 1) // エラー : `op' への参照が曖昧
twice((op = op + 1)) // 代入、名前付き引数ではない
twice({op = op + 1}) // 代入
twice{ op = op + 1 } // 代入
2.1 他フィーチャーとの統合 (Integration with other features)
次のリストは、名前付き引数が Scala 言語の他フィーチャーにどのような影響を与えるかを示します。:
名前呼び出しパラメータ (By-Name Parameters)
名前呼び出しパラメータは、名前付き引数を使っているときでも、期待通り依然機能します。 式は、メソッド本体がパラメータにアクセスするときのみ(そして繰り返し)評価されます。
反復パラメータ (Repeated Parameters)
アプリケーションが名前付き引数を使うとき、反復パラメータは正確に 1 度だけ指定されなければなりません。 同じパラメータ名を何度も使うことは許されません。
関数値 (Functional values)
Scala における関数値(functional value)は、apply と呼ばれるメソッドを実装したクラスのインスタンスです。名前付きの適用(named application)に対して、その apply メソッドのパラメータ名を使えます。 その静的な型が scala.FunctionN である関数値に対して、その apply メソッドのパラメータ名を使えます。
val f1 = new { def apply(x: Int) = x + 1 }
val f2 = (x: Int) => x + 1 // Function1[Int, Int] のインスタンス
f1(x = 2) // OK
// f2(x = 2) // "エラー : not found: value x"
f2(v1 = 2) // OK, 'v1' は Function1 中のパラメータ名
オーバーライド (Overriding)
サブクラス中でメソッドをオーバライドする(あるいは抽象メソッドを実装する)時、パラメータ名はスーパークラス中のものと同じである必要はありません。 名前付き引数を使う適用の型チェックについて、メソッドの静的な型に応じて使われる名前が決まります。
trait A { def f(a: Int): Int }
class B extends A { def f(x: Int) = x }
val a: A = new B
a.f(a = 1) // OK
オーバーロードの解決 (Overloading Resolution)
When a method application refers to an overloaded method, first the set of applicable alternatives is determined and then the most specific alternative is chosen (see [1], Chapter 6.25.3) . The presence of named argument influences the set of applicable alternatives, the argument types have to be matched against the corresponding parameter types based on the names.
メソッド適用がオーバーロードされたメソッドを参照するとき、最初に、適用可能な代替物の集合が決定され、次に、最も特化した(most specific)代替物が選択されます([1]、§ 6.25.3 参照)。 名前付き引数の存在は、適用可能な代替物の集合に影響を与え、引数型は対応するパラメータ型に、名前に基づいてマッチしなければなりません。 次の例では、2 番目の代替物が適用可能です:
def f() // #1
def f(a: Int, b: String) // #2
f(b = "someString", a = 1) // using #2
もし複数の代替物が適用可能なら、最も特化したもの(most specific)が決定されます。 このプロセスは、特定の適用で使われる引数名に依存せず、ただメソッドのシグニチャだけを見ます(詳細な記述は、[1]、§ 6.25.3 参照)。
次の例で、代替物の両方とも適用可能ですが、どちらも他方より特化してはいません。 なぜなら、引数型は、引数名ではなくそれらの場所に基づいて比較されるからです。
def f(a: Int, b: String) // #1
def f(b: Object, a: Int) // #2
f(a = 1, b = "someString") // "エラー: オーバーロードされた定義への
// 曖昧な参照
無名関数 (Anonymous functions)
無名関数を生成するプレースホルダー構文は、名前付き引数でも機能するように展開されます。
def f(x: Int, y: String)
val g1: Int => Int = f(y = "someString", x = _)
val g2 = f(y = "someString", x = _: Int)
3 デフォルト引数 (Default arguments)
デフォルト引数をもつメソッドパラメータは "p:T = expr" の形をとります。メソッド適用でデフォルト引数を使う時にはいつでも expr が評価されます。 デフォルト引数を使うためには、メソッド適用において対応するパラメータは除外されなければなりません。
def f(a: Int, b: String = "defaultString", c: Int = 5)
f(1)
f(1, "otherString")
f(1, c = 10) // c は名前付き引数として指定される必要あり
デフォルト引数をもつすべてのパラメータに対して、デフォルト式を計算する合成メソッドが生成されます。 メソッド適用でデフォルト引数を使うとき、見つからないパラメータは、対応する合成メソッドへの呼び出しの形で引数リストに加えられます。
def f(a: Int = 1, b: String)
// メソッド生成 : def f$default$1 = 1
f(b = "3")
// 変換 : f(b = "3", a = f$default$1)
デフォルト引数は任意の式で構いません。 パラメータのスコープは、後に続くすべてのパラメータリスト(そしてメソッド本体)に及ぶので、デフォルト式は、先行するパラメータリストのパラメータ(ただし、同じパラメータリスト中の他のパラメータを除く)に依存できます。 前のパラメータに依存するデフォルト値を使うときは、デフォルト引数ではなく実際の引数が使われることに注意してください。
def f(a: Int = 0)(b: Int = a + 1) = b // OK
// def f(a: Int = 0, b: Int = a + 1) // "エラー: 値 a が見つからない"
f(10)() // 11 を返す ( 1 ではない )
メソッドパラメータ "x:T = expr" のデフォルト引数 expr の型チェックについては、特別の要請型(expected type)が使われます。それは、メソッドの型パラメータ(コンストラクタ用のクラスの型パラメータ)のすべての出現を未定義の型で置き換えて得られるものです。これにより、多相的メソッドとクラスに対してデフォルト引数を指定できます。
def f[T](a: T = 1) = a
f() // 1 : Int を返す
f("s") // "s": String を返す
def g[T](a: T = 1, b: T = "2") = b
g(a = "1") // OK, "2": String を返す
g(b = 2) // OK, 2 : Int を返す
g() // OK, "2": Any を返す
// g[Int]() // "エラー: 型不一致; Int 要請に対し String 発見"
class A[T](a: T = "defaultString")
new A() // A[String] のインスタンス生成
new A(1) // A[Int] のインスタンス生成
3.1 他フィーチャーとの統合 (Integration with other features)
次のリストは、デフォルト引数が Scala 言語の他のフィーチャーにどのような影響を与えるかを示します。:
名前呼び出しパラメータ (By-Name Parameters)
名前呼び出しパラメータ上のデフォルト引数は期待通り機能します。 もし適用がデフォルト引数つきの名前呼び出しパラメータを指定していないなら、デフォルト式は、メソッド本体がそのパラメータを参照する時にはいつも、評価されます。
反復パラメータ (Repeated Parameters)
反復パラメータで終わるパラメータ部中でデフォルト引数を指定することは許されていません。
オーバーライド (Overriding)
デフォルト引数をもつメソッドが、オーバライドされるかあるいは、サブクラスで実装されるとき、すべてのデフォルトは継承され、サブクラスで利用できます。 サブクラスはデフォルト引数もオーバライドでき、スーパークラス中でデフォルトをもたないパラメータに新しいデフォルトを加えることができます。
型チェックの間、パラメータがデフォルト値を持っているかどうかを決定するのに、静的な型が使われます。 実行時、デフォルトの使用はメソッド呼び出しに変換されるので、デフォルト値はレシーバオブジェクトの動的な型に応じて決定されます。
trait A { def f(a: Int = 1, b: Int): (Int, Int) }
// B: 継承してデフォルトを加える
class B extends A { def f(a: Int, b: Int = 2) = (a, b) }
// C: デフォルトをオーバライド
class C extends A { def f(a: Int = 3, b: Int ) = (a, b) }
val a1: A = new B
val a2: A = new C
// a1.f() // "エラー : 未指定のパラメータ : value b"
a2.f(b = 2) // (3, 2) が返る
オーバーロード (Overloading)
もし、メソッドのオーバーロードされた代替物が複数あるなら、多くても 1 つだけがデフォルト引数を指定できます。
オーバーロードの解決 (Overloading Resolution)
メソッド適用式中に複数のオーバーロードされた代替物が適用可能であるとき、デフォルト引数を使う代替物は決して選択されません。
def f(a: Object) // #1
def f(a: String, b: Int = 1) // #2
f("str") // 双方とも適用可能、#1 が選ばれる
ケースクラス (Case Classes)
すべてのケースクラスに対し、"copy"という名前のメソッドが作成され、それによりクラスのインスタンスの修正コピーを容易に生成できます。 copy メソッドはケースクラスの主コンストラクタと同じ型と値パラメータをとり、すべてのパラメータのデフォルトを対応するコンストラクタ・パラメータへ持ち込みます。
case class A[T](a: T, b: Int) {
// def copy[T'](a': T' = a, b': Int = b): A[T'] =
// new A[T'](a', b')
}
val a1: A[Int] = A(1, 2)
val a2: A[String] = a1.copy(a = "someString")
copy メソッドは、"copy" という名前のメンバーが既にそのクラスあるいは、その親の 1 つに存在しない場合にだけ、追加されます。 これは、ケースクラスがもう 1 つのケースクラスの拡張であるとき、ただ 1 つの copy メソッドがあるということ、すなわち、それは階層構造中で最も低位のケースクラスからのものであることを意味します。
暗黙のパラメータ (Implicit Parameters)
暗黙のパラメータにデフォルト引数を指定できます。 これらのデフォルトは、パラメータ型に一致する暗黙の値が見つからない場合に使われます。
def f(implicit a: String = "value", y: Int = 0) = a +": "+ y
implicit val s = "size"
println(f) // "size: 0" と印字
4 実装
4.1 名前付き引数 (Named arguments)
名前付き引数を使うとき、引数の順番は、メソッド定義のパラメータ順と一致する必要はありません。 呼び出し場所順で引数式を評価するために、メソッド適用は次のようにブロックに変換されます:
class A {
def f(a: Int, b: Int)(c: Int)
}
(new A).f(b = getB(), a = getA())(c = getC())
// 次へ変換される
// {
// val qual$1 = new A()
// val x$1 = getB()
// val x$2 = getA()
// val x$3 = getC()
// qual$1.f(x$2, x$1)(x$3)
// }
4.2 デフォルト引数 (Default arguments)
コンパイラはすべてのデフォルト引数式に対して、その式を計算するメソッドを生成します。それらのメソッドは、メソッド名と文字列 "$default $"、パラメータ位置を示す数等からなる、ただ 1 つに決まる名前をもちます。 各メソッドは、オリジナルのメソッドの型パラメータと、対応するパラメータに先行する値パラメータ部によってパラメータ化されます:
def f[T](a: Int = 1)(b: T = a + 1)(c: T = b)
// 次が作成される :
// def f$default$1[T]: Int = 1
// def f$default$2[T](a: Int): Int = a + 1
// def f$default$3[T](a: Int)(b: T): T = b
コンストラクタのデフォルトについて、それらのメソッドは、クラスの(もしそれが存在しないなら、生成される)コンパニオンオブジェクトに加えられます。 他のメソッドについては、デフォルトメソッドはオリジナルのメソッドと同じ場所に生成されます。
デフォルト引数を使うメソッド呼び出しは、上述の名前付き引数と同じ形のブロックに変換されます:
f()("str")()
// transformed to:
// {
// val x$1 = f$default$1
// val x$2 = "str"
// val x$3 = f$default$3(x$1)(x$2)
// f(x$1)(x$2)(x$3)
// }
参考文献
最終更新:2011年04月05日 09:21