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

发布于 2024-10-11 01:03:15 字数 755 浏览 11 评论 0原文

虽然在某些有效情况下,此类方法重载可能会变得不明确,但为什么编译器不允许在编译时和运行时都不是不明确的代码呢?

例子:

// 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 方法后发现规范/编译器强加了任意限制,这一点并不好。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

生活了然无味 2024-10-18 01:03:15

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

:会是这样的:

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

有人愿意编写 SIP 提案

I'd like to cite Lukas Rytz (from here):

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, e.g. in this case:

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?

转身以后 2024-10-18 01:03:15

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

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.

滥情稳全场 2024-10-18 01:03:15

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

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
}

如果你有两个很长的参数列表,其中只有一个参数不同,那么可能值得麻烦......

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

绮筵 2024-10-18 01:03:15

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

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

What worked for me is to redefine (Java-style) the overloading methods.

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.

带上头具痛哭 2024-10-18 01:03:15

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

Here is a generalization of @Landei answer:

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 ..."
扛刀软妹 2024-10-18 01:03:15

一种可能的情况是


  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

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

只是我的猜测:-)

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:-)

情栀口红 2024-10-18 01:03:15

我的理解是,使用默认参数值编译的类中可能存在名称冲突。我在几个线程中看到了类似的内容。

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

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

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:

def foo(a: String)(b: Int) =  a + (if (b > 0) b else 42)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文