web-dev-qa-db-ja.com

なぜScalaコンパイラはデフォルト引数を持つオーバーロードされたメソッドを許可しないのですか?

このようなメソッドのオーバーロードがあいまいになる可能性がある有効なケースがあるかもしれませんが、コンパイラーはコンパイル時でも実行時でもあいまいでないコードを許可しないのはなぜですか?

例:

// This fails:
def foo(a: String)(b: Int = 42) = a + b
def foo(a: Int)   (b: Int = 42) = a + b

// This fails, too. Even if there is no position in the argument list,
// where the types are the same.
def foo(a: Int)   (b: Int = 42) = a + b
def foo(a: String)(b: String = "Foo") = a + b

// This is OK:
def foo(a: String)(b: Int) = a + b
def foo(a: Int)   (b: Int = 42) = a + b    

// Even this is OK.
def foo(a: Int)(b: Int) = a + b
def foo(a: Int)(b: String = "Foo") = a + b

val bar = foo(42)_ // This complains obviously ...

これらの制限を少し緩めることができない理由はありますか?

特に、過負荷のJavaコードをScalaに変換する場合、デフォルトの引数は非常に重要であり、多くのJavaメソッドby one Scala spec/compilerが任意の制限を課すメソッド。

130
soc

Lukas Rytzを引用したい( here から):

その理由は、デフォルト引数を返す生成されたメソッドに決定論的な命名スキームが必要だったからです。書くなら

def f(a: Int = 1)

コンパイラが生成します

def f$default$1 = 1

同じパラメーター位置にデフォルトを持つ2つのオーバーロードがある場合、異なる命名スキームが必要になります。しかし、生成されたバイトコードを複数のコンパイラーの実行にわたって安定させたいと考えています。

将来のScalaバージョンの解決策は、タイプ名をデフォルト以外の引数(メソッドの先頭にある曖昧さをなくすもの)を組み込むことです。オーバーロードされたバージョン)ネーミングスキーマに、たとえばこの場合:

def foo(a: String)(b: Int = 42) = a + b
def foo(a: Int)   (b: Int = 42) = a + b

次のようなものになります。

def foo$String$default$2 = 42
def foo$Int$default$2 = 42

SIPプロポーザル

107
Eugen Labun

デフォルト引数と解像度のオーバーロードの相互作用について、読みやすく正確な仕様を取得することは非常に困難です。もちろん、ここで紹介したような多くの個々のケースでは、何が起こるべきかを言うのは簡単です。しかし、それだけでは十分ではありません。すべての可能なコーナーケースを決定する仕様が必要です。オーバーロードの解像度を指定することはすでに非常に困難です。ミックスにデフォルト引数を追加すると、さらに難しくなります。そのため、2つを分離することにしました。

66
Martin Odersky

あなたの質問に答えることはできませんが、回避策は次のとおりです。

implicit def left2Either[A,B](a:A):Either[A,B] = Left(a)
implicit def right2Either[A,B](b:B):Either[A,B] = Right(b)

def foo(a: Either[Int, String], b: Int = 42) = a match {
  case Left(i) => i + b
  case Right(s) => s + b
}

1つの引数のみが異なる2つの非常に長い引数リストがある場合は、問題を解決する価値があるかもしれません...

11
Landei

私のために働いたのは、オーバーロードメソッドを再定義(Javaスタイル)することです。

def foo(a: Int, b: Int) = a + b
def foo(a: Int, b: String) = a + b
def foo(a: Int) = a + "42"
def foo(a: String) = a + "42"

これにより、現在のパラメーターに応じてコンパイラーが必要な解像度を確保できます。

6
belka

@Landeiの回答の一般化は次のとおりです。

本当に欲しいもの:

def pretty(tree: Tree, showFields: Boolean = false): String = // ...
def pretty(tree: List[Tree], showFields: Boolean = false): String = // ...
def pretty(tree: Option[Tree], showFields: Boolean = false): String = // ...

回避策

def pretty(input: CanPretty, showFields: Boolean = false): String = {
  input match {
    case TreeCanPretty(tree)       => prettyTree(tree, showFields)
    case ListTreeCanPretty(tree)   => prettyList(tree, showFields)
    case OptionTreeCanPretty(tree) => prettyOption(tree, showFields)
  }
}

sealed trait CanPretty
case class TreeCanPretty(tree: Tree) extends CanPretty
case class ListTreeCanPretty(tree: List[Tree]) extends CanPretty
case class OptionTreeCanPretty(tree: Option[Tree]) extends CanPretty

import scala.language.implicitConversions
implicit def treeCanPretty(tree: Tree): CanPretty = TreeCanPretty(tree)
implicit def listTreeCanPretty(tree: List[Tree]): CanPretty = ListTreeCanPretty(tree)
implicit def optionTreeCanPretty(tree: Option[Tree]): CanPretty = OptionTreeCanPretty(tree)

private def prettyTree(tree: Tree, showFields: Boolean): String = "fun ..."
private def prettyList(tree: List[Tree], showFields: Boolean): String = "fun ..."
private def prettyOption(tree: Option[Tree], showFields: Boolean): String = "fun ..."
2

可能なシナリオの1つは


  def foo(a: Int)(b: Int = 10)(c: String = "10") = a + b + c
  def foo(a: Int)(b: String = "10")(c: Int = 10) = a + b + c

コンパイラは、どちらを呼び出すかについて混乱します。他の考えられる危険を防ぐために、コンパイラは、デフォルト引数を持つオーバーロードメソッドを1つまで許可します。

ちょうど私の推測:-)

1
Shiva Wu

私の理解では、デフォルトの引数値を持つコンパイルされたクラスで名前の衝突が発生する可能性があるということです。いくつかのスレッドで言及されているこれらの線に沿って何かを見てきました。

名前付き引数の仕様はこちら: http://www.scala-lang.org/sites/default/files/sids/rytz/Mon,%202009-11-09,%2017:29/named-args .pdf

状態:

 Overloading If there are multiple overloaded alternatives of a method, at most one is
 allowed to specify default arguments.

とりあえず、とりあえずはうまくいきません。

Javaでできることのようなことをすることができます。例えば:

def foo(a: String)(b: Int) =  a + (if (b > 0) b else 42)
0
Janx