为什么 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 默认参数是非常重要的,并且在用一个 Scala 方法替换了大量 Java 方法之后发现 spec/编译器强加了任意的限制是不好的。

36435 次浏览

我不能回答你的问题,但这里有一个变通办法:

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
}

如果您有两个非常长的参数列表,只有一个参数不同,这可能是值得的麻烦..。

对于重载解析与默认参数之间的交互,很难获得一个可读且精确的规范。当然,对于许多个别案例,比如这里提到的,很容易说出应该发生什么。但这还不够。我们需要一个规格,决定所有可能的角落案件。重载分辨率已经很难指定了。在这种混合中添加默认参数将使问题更加困难。这就是为什么我们选择将两者分开。

我的理解是,在具有默认参数值的编译类中可能存在名称冲突。我在几个线索中看到过类似的东西。

这里是命名参数规范: 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)

可能的情况之一是


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

编译器将不知道调用哪一个。为了防止其他可能的危险,编译器最多允许一个具有默认参数的重载方法。

只是我的猜测: -)

我想引用 Lukas Rytz (来自 给你) :

原因是我们需要一个确定性的命名方案 返回默认参数的生成方法

def f(a: Int = 1)

编译器生成

def f$default$1 = 1

如果在同一个参数上有两个默认重载 我们需要一个不同的命名方案。但我们希望保持 在多个编译器运行时生成的字节码稳定。

未来 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 建议书吗?

对我有效的方法是重新定义(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"

这样可以确保编译器根据当前参数选择所需的分辨率。

以下是@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 ..."