ChatGPT解决这个技术问题 Extra ChatGPT

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

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

例子:

// 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");}
@user1609012:你的把戏对我不起作用。我使用 Scala 2.12.0 和 Scala 2.11.8 进行了尝试。
恕我直言,这是 Scala 中最强的痛点之一。每当我尝试提供灵活的 API 时,我经常会遇到这个问题,尤其是在重载伴随对象的 apply() 时。虽然我更喜欢 Scala 而不是 Kotlin,但在 Kotlin 中你可以做这种重载......
这方面的记录票是 github.com/scala/bug/issues/8161

E
Eugen Labun

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


我认为您在这里的提议很有意义,而且我不认为指定/实施它会如此复杂。本质上,参数类型是函数 ID 的一部分。编译器当前对 foo(String) 和 foo(Int) 做了什么(即没有默认值的重载方法)?
从 Java 访问 Scala 方法时,这不会有效地引入强制性匈牙利表示法吗?似乎它会使接口变得非常脆弱,迫使用户在函数的类型参数发生变化时要小心。
另外,复杂类型呢? A with B,例如?
M
Martin Odersky

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


感谢您的回答。可能让我感到困惑的是,基本上在其他任何地方,编译器只会在实际存在歧义时才会抱怨。但是在这里编译器会抱怨,因为可能会出现类似的情况会出现歧义。因此,在第一种情况下,编译器仅在存在已证实的问题时才会抱怨,但在第二种情况下,编译器的行为不太精确,并且会为“看似有效”的代码触发错误。以最不惊讶的原则看到这一点,这有点可惜。
“很难获得可读且精确的规范 [...]”是否意味着如果有人加强了良好的规范和/或实施,当前情况可能会得到改善?目前的情况恕我直言,相当多地限制了命名/默认参数的可用性......
有一个提议对规范进行更改的过程。 scala-lang.org/node/233
I have some comments(请参阅我在链接答案下方的评论)关于 Scala 使重载不受欢迎和二等公民。如果我们继续故意削弱 Scala 中的重载,我们正在用名称代替打字,这在 IMO 是一个倒退的方向。
如果 Python 能做到,我看不出 Scala 不能做到的任何充分理由。复杂性的论据是一个很好的论据:从用户的角度来看,实现此功能将使 Scale 变得不那么复杂。阅读其他答案,您会看到人们发明非常复杂的东西只是为了解决从用户角度来看甚至不应该存在的问题。
L
Landei

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

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] 值时,都会接受 AB。如果您想朝这个方向发展,则应该定义一种仅由具有默认参数的函数(例如此处的 foo )接受的类型;当然,这是否是一个方便的解决方案变得更加不清楚。
b
belka

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

这可以确保编译器根据当前参数需要什么分辨率。


G
Guillaume Massé

这是@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 ..."

S
Shiva Wu

一种可能的情况是


  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

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

只是我的猜测:-)


J
Janx

我的理解是在编译的类中可能存在名称冲突与默认参数值。我已经在几个线程中看到了这些方面的内容。

命名参数规范在这里: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)

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅