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


148

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

例:

// 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メソッドを1つのScalaメソッドで置き換えた後、スペック/コンパイラーが任意の制限を課していることを知るのはよくありません。


18
「任意の制限」 :-)
KajMagnus

1
型引数を使用して問題を回避できるようです。これはコンパイルされます:object Test { def a[A](b: Int, c: Int, d: Int = 7): Unit = {}; def a[A](a:String, b: String = ""): Unit = {}; a(2,3,4); a("a");}
user1609012

@ user1609012:あなたのトリックは私にはうまくいきませんでした。Scala 2.12.0とScala 2.11.8を使って試してみました。
Landlocked Surfer 2017

4
私見これはScalaの最も強力な問題の1つです。柔軟なAPIを提供しようとするときはいつでも、特にコンパニオンオブジェクトのapply()をオーバーロードするときに、この問題によく遭遇します。KotlinよりもScalaを少し好みますが、Kotlinではこのようなオーバーロードを実行できます...
キュービックレタス

回答:


113

ルーカスリッツ(ここから)を引用します

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

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の提案書いてくれる人はいますか?


2
ここでのあなたの提案は非常に理にかなっていると思います、そして私はそれを指定/実装することについてそれほど複雑になるとは思いません。基本的に、パラメータタイプは関数のIDの一部です。コンパイラは現在foo(String)とfoo(Int)を使って何をしていますか(つまり、デフォルトなしのオーバーロードされたメソッド)。
マーク

これは、JavaからScalaメソッドにアクセスするときに必須のハンガリー表記法を効果的に導入しないのでしょうか インターフェースが非常に壊れやすくなり、関数の型パラメーターが変更されたときにユーザーに注意を払わなければならないようです。
blast_hardcheese 14

また、複合型についてはどうですか?A with B、 例えば?
blast_hardcheese 14

66

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


4
ご回答有難うございます。多分私を混乱させたのは、基本的に他のどこでもコンパイラーは実際に曖昧さがある場合にのみ不平を言うということでした。しかし、あいまいさが発生する可能性のある同様のケースが存在する可能性があるため、ここではコンパイラーが文句を言います。したがって、最初のケースでは、コンパイラーは実証済みの問題がある場合にのみ文句を言いますが、2番目のケースでは、コンパイラーの動作ははるかに正確でなく、「一見有効な」コードのエラーをトリガーします。これを最小の驚きの原則で見ると、これは少し残念です。
soc

2
「読みやすく正確な仕様を取得するのは非常に難しい[...]」ということは、誰かが適切な仕様や実装を進めれば、現在の状況が改善される可能性があるということを意味しますか?現在の状況では、名前付き/デフォルトパラメータの使いやすさがかなり制限されています...
soc

仕様の変更を提案するプロセスがあります。 scala-lang.org/node/233
James Iry、

2
私は Scalaが過負荷を眉をひそめ、二流の市民を作ることについて、いくつかのコメントがあります(リンクされた回答の下の私のコメントを参照)。Scalaでオーバーロードを意図的に弱め続けている場合は、タイピングを名前に置き換えます。これはIMOが逆方向です。
シェルビームーアIII

10
Pythonができても、Scalaができなかった理由はわかりません。複雑さについての議論は良いことです。この機能を実装すると、ユーザーの観点からScaleの複雑さが緩和されます。他の回答を読むと、ユーザーの視点からは存在してはならない問題を解決するためだけに、非常に複雑なものを発明している人がいます。
Richard Gomes 14

12

私はあなたの質問に答えることはできませんが、これは回避策です:

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つの非常に長い引数リストがある場合、問題を解決する価値があるかもしれません...


1
まあ、私はコードをより簡潔で読みやすくするためにデフォルトの引数を使用しようとしました...実際には、代替型を受け入れられた型に変換するだけの暗黙の変換をクラスに追加しました。それはただ醜く感じます。そして、デフォルトの引数を持つアプローチはうまくいくはずです!
soc

あなたは、彼らがすべての用途に適用することから、このような変換には注意する必要がありますEitherだけのためではなくfoo-たびに、この方法で、Either[A, B]値が要求され、両方AB受け入れられています。fooこの方向に移動したい場合は、代わりにデフォルト引数(ここにあるような)を持つ関数によってのみ受け入れられるタイプを定義する必要があります。もちろん、これが便利な解決策であるかどうかはさらにわかりにくくなります。
Blaisorblade、2012年

9

私にとってうまくいったのは、オーバーロードメソッドを(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"

これにより、現在のパラメーターに従ってコンパイラーに必要な解像度を保証します。


3

@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 ..."

1

考えられるシナリオの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つのオーバーロードされたメソッドがデフォルトの引数を持つことを許可します。

ちょうど私の推測:-)


0

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

名前付き引数の仕様はここにあります: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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.