虽然可能存在这样的方法重载可能变得模棱两可的有效情况,但为什么编译器不允许在编译时和运行时都不模棱两可的代码?
例子:
// 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 方法后发现规范/编译器施加了任意限制并不好。
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");}
我想引用 Lukas Rytz(来自 here):
原因是我们希望为返回默认参数的生成方法提供确定性的命名方案。如果你写 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
有人愿意write a SIP proposal吗?
对于重载决议与默认参数的交互,很难获得可读且精确的规范。当然,对于许多个别情况,例如此处介绍的情况,很容易说出应该发生的情况。但这还不够。我们需要一个规范来决定所有可能的极端情况。重载分辨率已经很难指定。在混合中添加默认参数会使其更加困难。这就是我们选择将两者分开的原因。
我无法回答您的问题,但这里有一个解决方法:
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
}
如果你有两个很长的 arg 列表,它们只有一个 arg 不同,那么麻烦可能是值得的......
Either
的所有用途,而不仅仅是 foo
- 这样,每当请求 Either[A, B]
值时,都会接受 A
和 B
。如果您想朝这个方向发展,则应该定义一种仅由具有默认参数的函数(例如此处的 foo
)接受的类型;当然,这是否是一个方便的解决方案变得更加不清楚。
对我有用的是重新定义(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 ..."
一种可能的情况是
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
编译器会对调用哪一个感到困惑。为了防止其他可能的危险,编译器最多允许一个重载方法具有默认参数。
只是我的猜测:-)
我的理解是在编译的类中可能存在名称冲突与默认参数值。我已经在几个线程中看到了这些方面的内容。
命名参数规范在这里: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)
A with B
,例如?