简体   繁体   English

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

[英]Why does the Scala compiler disallow overloaded methods with default arguments?

While there might be valid cases where such method overloadings could become ambiguous, why does the compiler disallow code which is neither ambiguous at compile time nor at run time?虽然可能存在这样的方法重载可能变得模棱两可的有效情况,但为什么编译器不允许在编译时和运行时既不模棱两可的代码呢?

Example:例子:

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

Are there any reasons why these restrictions can't be loosened a bit?有什么理由不能放宽这些限制吗?

Especially when converting heavily overloaded Java code to Scala default arguments are a very important and it isn't nice to find out after replacing plenty of Java methods by one Scala methods that the spec/compiler imposes arbitrary restrictions.特别是当将重载的 Java 代码转换为 Scala 的默认参数非常重要时,在用一个 Scala 方法替换大量 Java 方法后发现规范/编译器强加了任意限制并不好。

I'd like to cite Lukas Rytz (from here ):我想引用 Lukas Rytz(从这里):

The reason is that we wanted a deterministic naming-scheme for the generated methods which return default arguments.原因是我们想要一个确定性的命名方案来生成返回默认参数的方法。 If you write如果你写

def f(a: Int = 1)

the compiler generates编译器生成

def f$default$1 = 1

If you have two overloads with defaults on the same parameter position, we would need a different naming scheme.如果在同一参数位置有两个具有默认值的重载,我们将需要不同的命名方案。 But we want to keep the generated byte-code stable over multiple compiler runs.但是我们希望在多次编译器运行中保持生成的字节码稳定。

A solution for future Scala version could be to incorporate type names of the non-default arguments (those at the beginning of a method, which disambiguate overloaded versions) into the naming schema, eg in this case:未来 Scala 版本的解决方案可能是将非默认参数的类型名称(在方法开头的那些,消除重载版本的歧义)合并到命名模式中,例如在这种情况下:

def foo(a: String)(b: Int = 42) = a + b
def foo(a: Int)   (b: Int = 42) = a + b

it would be something like:它会是这样的:

def foo$String$default$2 = 42
def foo$Int$default$2 = 42

Someone willing to write a SIP proposal ?有人愿意写 SIP 提案吗?

It would be very hard to get a readable and precise spec for the interactions of overloading resolution with default arguments.对于重载解析与默认参数的交互,很难获得可读且精确的规范。 Of course, for many individual cases, like the one presented here, it's easy to say what should happen.当然,对于许多个别情况,就像这里介绍的情况一样,很容易说出应该发生什么。 But that is not enough.但这还不够。 We'd need a spec that decides all possible corner cases.我们需要一个规范来决定所有可能的极端情况。 Overloading resolution is already very hard to specify.重载分辨率已经很难指定。 Adding default arguments in the mix would make it harder still.在混合中添加默认参数会使它变得更加困难。 That's why we have opted to separate the two.这就是我们选择将两者分开的原因。

I can't answer your question, but here is a workaround:我无法回答您的问题,但这里有一个解决方法:

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
}

If you have two very long arg lists which differ in only one arg, it might be worth the trouble...如果您有两个非常长的 arg 列表,它们仅在一个 arg 中不同,那么麻烦可能值得...

What worked for me is to redefine (Java-style) the overloading methods.对我有用的是重新定义(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"

This ensures the compiler what resolution you want according to the present parameters.这可以确保编译器根据当前参数获得您想要的分辨率。

Here is a generalization of @Landei answer:这是@Landei 答案的概括:

What you really want:你真正想要的是:

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

One of the possible scenario is一种可能的情况是


  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

The compiler will be confused about which one to call.编译器会对调用哪一个感到困惑。 In prevention of other possible dangers, the compiler would allow at most one overloaded method has default arguments.为了防止其他可能的危险,编译器将允许最多一个重载方法具有默认参数。

Just my guess:-)只是我的猜测:-)

My understanding is that there can be name collisions in the compiled classes with default argument values.我的理解是在编译的类中可能会存在具有默认参数值的名称冲突。 I've seen something along these lines mentioned in several threads.我在几个线程中看到了沿着这些方向的一些东西。

The named argument spec is here: http://www.scala-lang.org/sites/default/files/sids/rytz/Mon,%202009-11-09,%2017:29/named-args.pdf命名参数规范在这里: http : //www.scala-lang.org/sites/default/files/sids/rytz/Mon,%202009-11-09,%2017 :29/named-args.pdf

It states:它指出:

 Overloading If there are multiple overloaded alternatives of a method, at most one is
 allowed to specify default arguments.

So, for the time being at any rate, it's not going to work.所以,目前无论如何,它是行不通的。

You could do something like what you might do in Java, eg:你可以做一些你可能在 Java 中做的事情,例如:

def foo(a: String)(b: Int) =  a + (if (b > 0) b else 42)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM