为什么Scala编译器不允许使用默认参数重载方法?

时间:2011-01-10 22:09:50

标签: scala methods default overloading

虽然可能存在这样的方法重载可能变得模糊的有效情况,但为什么编译器不允许在编译时和运行时都不明确的代码?

示例:

// 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方法后发现规范/编译器强加任意限制是不好的。

7 个答案:

答案 0 :(得分:106)

我想引用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

答案 1 :(得分:66)

对于重载分辨率与默认参数的交互,获取可读且精确的规范将非常困难。当然,对于许多个别情况,例如这里介绍的情况,很容易说出应该发生什么。但这还不够。我们需要一个决定所有可能的极端情况的规范。重载分辨率已经非常难以指定。在混合中添加默认参数会使其更难。这就是我们选择将两者分开的原因。

答案 2 :(得分:10)

我无法回答你的问题,但这是一个解决方法:

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不同,那可能值得这么麻烦......

答案 3 :(得分:4)

对我来说有用的是重新定义(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"

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

答案 4 :(得分: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

编译器会对要调用哪一个感到困惑。在防止其他可能的危险时,编译器最多允许一个重载方法具有默认参数。

我的猜测: - )

答案 5 :(得分:1)

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

Workarround

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

答案 6 :(得分: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)