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

soc*_*soc 132 methods default scala 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 ...
Run Code Online (Sandbox Code Playgroud)

是否有任何理由不能放松这些限制?

特别是在将重载过多的Java代码转换为Scala时,默认参数是非常重要的,在通过一个Scala方法替换大量Java方法之后发现规范/编译器强加任意限制并不好.

Eug*_*bun 107

我想引用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
Run Code Online (Sandbox Code Playgroud)

它会是这样的:

def foo$String$default$2 = 42
def foo$Int$default$2 = 42
Run Code Online (Sandbox Code Playgroud)

有人愿意写一个SIP提案吗?

  • 我认为你的建议在这里很有意义,我不知道在指定/实现它时会有多么复杂.实质上,参数类型是函数ID的一部分.编译器目前对foo(String)和foo(Int)做了什么(即,没有默认值的重载方法)? (2认同)

Mar*_*sky 66

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

  • 如果Python可以做到,我看不出Scala无法做到的任何理由.复杂性的论点很好:从用户的角度来看,实现此功能会使Scale变得更简单.阅读其他答案,你会看到人们发明非常复杂的东西只是为了解决从用户的角度来看甚至不存在的问题. (8认同)
  • 感谢您的回答.可能让我困惑的事情是,基本上在其他任何地方编译器只会抱怨如果确实存在一些歧义.但是编译器在这里抱怨,因为可能会出现类似的情况,可能会出现歧义.所以在第一种情况下,编译器只会抱怨是否存在已证实的问题,但在第二种情况下,编译器行为的精确度要低得多,并且会触发"看似有效"代码的错误.以最不惊讶的原则看到这一点,这有点不幸. (4认同)
  • "获得可读且精确的规范是非常困难的"这意味着,如果有人采用良好的规范和/或实施,那么当前情况有可能得到改善吗?目前的情况imho限制命名/默认参数的可用性相当多... (2认同)
  • [我有一些评论](http://stackoverflow.com/a/2512001/615784)(请参阅下面关于链接答案的评论)关于Scala使重载不满和二等公民.如果我们继续故意削弱Scala中的重载,我们正在用名称替换打字,IMO是一个回归方向. (2认同)

Lan*_*dei 11

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

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
}
Run Code Online (Sandbox Code Playgroud)

如果你有两个非常长的arg列表,只有一个arg不同,那么它可能值得一试......


bel*_*lka 6

对我有用的是重新定义(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"
Run Code Online (Sandbox Code Playgroud)

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