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

为什么Scala编译器不允许带有默认参数的重载方法?,scala,methods,default,overloading,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 ar

虽然在某些有效的情况下,这样的方法重载可能会变得模棱两可,但为什么编译器不允许在编译时和运行时都不模棱两可的代码呢

例如:

// 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/编译器施加了任意限制是不好的。

我无法回答您的问题,但这里有一个解决方法:

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
}

如果您有两个很长的参数列表,它们只在一个参数上有所不同,那么这可能是值得的…

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

我的理解是,使用默认参数值编译的类中可能存在名称冲突。我在几篇文章中看到过类似的东西

命名参数规范如下所示:

它说:

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


我猜:-)

我想引用卢卡斯·雷茨(摘自:

原因是我们想要一个确定性的命名方案 生成返回默认参数的方法。如果你写信

def(a:Int=1)

编译器生成

def$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

有人愿意?

对我有效的是重新定义(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 answer的一个概括:

你真正想要的是:

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

嗯,我尝试使用默认参数使我的代码更简洁易读。。。实际上,我在一个例子中向类添加了一个隐式转换,它只是将替代类型转换为可接受的类型。感觉很难看。使用默认参数的方法应该可以正常工作!您应该小心此类转换,因为它们适用于
other
的所有用途,而不仅仅适用于
foo
——这样,每当请求
other[a,B]
值时,
a
B
都被接受。如果你想这样做的话,你应该定义一个只有默认参数的函数才能接受的类型(比如这里的
foo
);当然,这是否是一个方便的解决方案就更不清楚了。谢谢你的回答。可能让我困惑的是,基本上在其他任何地方,编译器都只会在有歧义的情况下抱怨。但在这里,编译器会抱怨,因为可能会有类似的情况出现歧义。因此,在第一种情况下,编译器只会在存在已证实的问题时进行投诉,而在第二种情况下,编译器的行为就不那么精确,并且会触发“似乎有效”代码的错误。从最不令人惊讶的原则来看,这有点不幸。“很难获得一个可读且精确的规范[…]”是否意味着如果有人提出一个好的规范和/或实现,当前的情况可能会得到改善?目前的情况在很大程度上限制了命名/默认参数的可用性……有一个过程可以建议对规范进行更改(参见下面链接的答案中我的评论),Scala使重载成为一个二等公民。如果我们继续故意削弱Scala中的重载,我们将用名称取代键入,这在我看来是一个倒退的方向。如果Python可以做到,我看不出Scala不能做到的任何好理由。复杂性的理由是很好的:从用户的角度来看,实现此功能将降低规模的复杂性。阅读其他答案,你会看到人们发明了非常复杂的东西,只是为了解决一个从用户角度看根本不应该存在的问题。“任意限制”:-,看起来你可以使用类型参数绕过这个问题。这编译:
对象测试{defa[a](b:Int,c:Int,d:Int=7):Unit={};defa[a](a:String,b:String=“”):Unit={};a(2,3,4);a(“a”);}
@user1609012:你的把戏对我不起作用。我使用Scala 2.12.0和Scala 2.11.8.IMHO进行了尝试,这是Scala中最严重的痛点之一。每当我试图提供一个灵活的API时,我经常遇到这个问题,特别是在重载伴随对象的apply()时。虽然我有点喜欢Scala而不是Kotlin,但在Kotlin中,你可以做这种重载……记录在案的是,我认为你的建议很有道理,我不认为有什么会如此复杂