为什么要“避免方法重载”?

80

这是否与Scala继承了Java的类型擦除有关? - Justin Niessner
1
@Justin:类型擦除与此有什么关系? - missingfaktor
6
为什么不去问Jorge Ortiz为什么建议不要使用方法重载? - John
13
抱歉,我无法打开该链接并翻译其中的表情符号。如果您能提供更多上下文或其他需要翻译的内容,我将尽力帮助您进行翻译。 - missingfaktor
Jorge Ortiz的文章链接似乎已经失效了,还有其他地方可以找到吗? - Roland
显示剩余6条评论
4个回答

111

重载使将方法提升为函数变得有点困难:

object A {
   def foo(a: Int) = 0
   def foo(b: Boolean) = 0
   def foo(a: Int, b: Int) = 0

   val function = foo _ // fails, must use = foo(_, _) or (a: Int) => foo(a)
}

您无法选择性地导入一组重载方法中的其中一个。

当尝试应用隐式视图来调整参数以适应参数类型时,可能会更容易出现歧义:

scala> implicit def S2B(s: String) = !s.isEmpty                             
S2B: (s: String)Boolean

scala> implicit def S2I(s: String) = s.length                               
S2I: (s: String)Int

scala> object test { def foo(a: Int) = 0; def foo(b: Boolean) = 1; foo("") }
<console>:15: error: ambiguous reference to overloaded definition,
both method foo in object test of type (b: Boolean)Int
and  method foo in object test of type (a: Int)Int
match argument types (java.lang.String)
       object test { def foo(a: Int) = 0; def foo(b: Boolean) = 1; foo("") }

它可以悄悄地使默认参数无法使用:
object test { 
    def foo(a: Int) = 0; 
    def foo(a: Int, b: Int = 0) = 1 
}

Individually, 这些原因并不足以迫使你完全避免重载。我感觉自己错过了一些更大的问题。
更新
证据越来越多。
- 它使spec变得复杂。 - 它可以使render implicits unsuitable不能在视图边界中使用。 - 它仅限于在重载的替代方案中引入参数的默认值之一。 - 由于参数将没有预期的类型,您无法像'_.foo' as arguments to overloaded methods那样传递匿名函数文字。 -
更新2
- 您目前无法在软件包对象中使用重载方法。 - API调用者的可适用性错误harder to diagnose
更新3
- 静态重载解析可能会剥夺API的所有类型安全性:
scala> object O { def apply[T](ts: T*) = (); def apply(f: (String => Int)) = () }
defined object O

scala> O((i: String) => f(i)) // oops, I meant to call the second overload but someone changed the return type of `f` when I wasn't looking...

2
目前,scalac也存在一个bug,在某些情况下会被重载触发。https://issues.scala-lang.org/browse/SI-7596。 - cvogt
2
前两个问题并不影响重载的每一个有效使用。已经为第三个问题提交了一个错误报告默认值的限制是有意为之,理论上可以修复。_.foo问题的故障是Scala的有限类型推断,而不是重载。你回答了这个问题,但其中一些原因是由于Scala中其他弱点,这些弱点可以得到改进。重载比运行时下转换一个分离或名称的笛卡尔积更高效,或者说嘈杂且与共享语义脱节。 - Shelby Moore III
3
由于缺乏一流联合类型,目前无法普遍使用类型类(Typeclasses)。尽管我注意到了Scala社区中的这种态度,但是可以想象一下,如果用名称的笛卡尔积代替每个公共语义的静态类型,比如addIntToDoubleaddDoubleToInt,那将会怎样。用名称替换类型似乎是倒退的。与其说Scala做得好,不如说Java做得更好 - Shelby Moore III
2
在我之前的评论中提到的 bug 报告的讨论线程中,我写道:"在我的看法中,邪恶的是期望重载成为它不是的东西,或者减少拥有一个通用语义名称的重要性。" - Shelby Moore III
2
答案和评论对我来说似乎非常幼稚,而且没有提到使用重载的最重要原因:当一个方法实际上必须根据传递给它的类型执行非常不同的内部逻辑时。 “使用隐式”这个答案立即失败,因为可能不存在从一个对象到具有专门逻辑的不同类型的任何可能转换。 - ely
显示剩余4条评论

8

如果可能的话,避免重载是一个很好的选择,Gilad和Jason(retronym)所给出的原因都非常充分。Gilad的原因主要集中在为什么重载在一般情况下都是有问题的,而Jason的原因则更多地关注于它在其他Scala特性的背景下为何有问题。

在Jason的列表中,我还要补充一点,那就是重载与类型推断相互作用不佳。考虑以下代码:

val x = ...
foo(x)

对于 x 推断类型的更改可能会改变调用哪个 foo 方法。 x 的值不需要更改,只需更改推断类型,这可能发生因为各种各样的原因。

出于所有给出的原因(以及我肯定忘记的一些原因),我认为应尽可能少地使用方法重载。


2
如果你不想发生这种情况,你需要为x声明类型。如果你不声明它,那么你就是在说你希望它改变。每个具有相同参数数量的重载函数的语义应该相同,否则它设计得不正确。至于限制奇怪的推断级联范围的问题,公共方法应该总是声明它们的返回类型。我认为这是影响Scala版本之间二进制兼容性的问题之一。 - Shelby Moore III

1

我认为这个建议并不是特别针对Scala,而是针对面向对象编程(OO)的一般性建议(据我所知,Scala被认为是OO和函数式编程之间最好的结合)。

覆盖是可以接受的,它是多态的核心,也是OO设计的核心。

重载则更加棘手。使用方法重载时,很难确定哪个方法会被真正调用,这通常会导致混淆。而且很少有正当理由需要使用重载。大多数情况下,问题都可以通过其他方式解决,我同意重载是一种“坏味道”。

这里有一篇文章,很好地解释了我所说的“重载是混淆的根源”,我认为这也是为什么要避免使用重载的主要原因。虽然这篇文章是针对Java的,但我认为它同样适用于Scala。


2
而且Scala并不是一个主要的面向对象编程语言。 - Daniel Earwicker
4
@ewenli - 豪尔赫在 scala 社区中非常有名,Rahul 提供的链接是豪尔赫的一个 scala 技巧,然而你的回答并没有针对 scala 特别不适合 函数重载这个问题提供任何建议。此外,我不知道为什么你认为这个问题有所困惑 - 你应该从回答中删除这部分,因为它完全没有根据。-1 - oxbow_lakes
7
@Daniel Scala主要是一种面向对象编程语言。您为什么认为不是呢? - Daniel C. Sobral
3
哦,一种与之竞争的函数式编程语言创造者认为Scala不够函数式!这并不意外! :) - Daniel Earwicker
3
@Daniel Scala可能是多范式的语言,但它仍然是_主要_面向对象的语言。Scala的函数特性是通过实现面向对象的特性来实现的。即使是最功能化的Scala程序也仅由对象及其相应的类和特征组成。现在,Martin Odersky可以随便说他想说的关于他的语言(毕竟这是_他的_语言),但在严格的技术评估中,Scala主要是面向对象的,我所指的“主要”是指其他所有的特性都是基于这个特性构建的。 - Daniel C. Sobral
显示剩余18条评论

1
我最近写了一篇关于这个问题的博客:方法重载的隐藏危险。我认为方法重载可能会对代码质量产生负面影响,因为在相同的名称下维护两个或更多的实现会导致隐含的语义,这不可避免地会导致误解和功能缺陷。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接