函数重载?是或否[关闭]


17

我正在开发一种静态和强类型的编译语言,并且正在重新考虑是否将函数重载作为一种语言功能包括在内的想法。我意识到我有点偏颇,主要来自C[++|#]背景。

支持反对在语言包含函数重载的最有说服力的论据什么?


编辑:没有人有反对意见吗?

伯特兰·迈耶(Bertrand Meyer,埃菲尔铁塔的创建者早在1985/1986年)就将方法重载称为:(源)

一种虚荣机制,不会使OO语言的语义能力发挥任何作用,但会妨碍可读性并使每个人的任务变得复杂

现在这些是一些笼统的概括,但是他是一个聪明的人,所以我可以肯定地说他可以根据需要支持它们。实际上,他几乎让Brad Abrams(CLSv1开发人员之一)确信.NET不应该支持方法重载。(来源)这是一些有力的东西。谁能说明他的思想,以及25年后他的观点是否仍然合理?

Answers:


24

函数重载对于C ++样式的模板代码绝对至关重要。如果必须对不同类型使用不同的函数名称,则无法编写通用代码。这将消除C ++库中大量且频繁使用的部分以及C ++的许多功能。

它通常存在于成员函数名称中。 A.foo()可以从中调用完全不同的函数B.foo(),但是两个函数都被命名foo。它存在于运算符中,+应用于整数和浮点数时也是如此,它通常用作字符串连接运算符。不允许在常规功能中使用它似乎很奇怪。

它允许使用Common Lisp风格的“多重方法”,其中调用的确切函数取决于两种数据类型。如果尚未在Common Lisp对象系统中进行编程,请尝试使用它,然后再将此称为无用。这对于C ++流至关重要。

没有函数重载(或可变参数函数,更糟)的I / O将需要许多不同的函数,以打印不同类型的值或将不同类型的值转换为通用类型(如String)。

没有函数重载,如果我更改某些变量或值的类型,则需要更改使用它的每个函数。这使得重构代码变得更加困难。

当用户不必记住正在使用哪种类型命名约定,并且用户仅可以记住标准函数名称时,它使使用API​​更加容易。

没有操作符重载,如果该基本操作可以在多个类型上使用,则我们必须用其使用的类型标记每个函数。这本质上是匈牙利表示法,是糟糕的做法。

总体而言,它使语言更加实用。


1
+1,所有优点。相信我,我不认为多重方法是没有用的...每当我被迫使用访客模式时,我都会诅咒键入的键盘。
注意:想起名字

这个家伙不是在描述函数重写而不是重载吗?
dragosb

8

我建议至少了解Haskell中的类型类。类型类的创建是一种针对操作员重载的规范方法,但它发现了其他用途,并在一定程度上使Haskell成为了它。

例如,这是临时重载的示例(不是很有效的Haskell):

(==) :: Int -> Int -> Bool
x == y = ...
x /= y = not (x == y)

(==) :: Char -> Char -> Bool
x == y = ...
x /= y = not (x == y)

这是使用类型类重载的相同示例:

class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool

    x /= y  =  not (x == y)

instance Eq Int where
    x == y  = ...

instance Eq Char where
    x == y  = ...

这样做的缺点是,你必须拿出你所有的类型类时髦的名字(例如在Haskell,你有相当抽象的MonadFunctorApplicative还有更简单,更容易识别EqNumOrd)。

一个好处是,一旦您熟悉类型类,便知道如何在该类中使用任何类型。另外,很容易保护未实现所需类的类型的函数,例如:

group :: (Eq a) => [a] -> [[a]]
group = groupBy (==)

编辑:在Haskell中,如果您想要一个==接受两种不同类型的运算符,则可以使用多参数类型类:

class Eq a b where
    (==) :: a -> b -> Bool
    (/=) :: a -> b -> Bool

    x /= y  =  not (x == y)

instance Eq Int Int where
    x == y  = ...

instance Eq Char Char where
    x == y  = ...

instance Eq Int Float where
    x == y  = ...

当然,这可能不是一个好主意,因为它明确允许您比较苹果和橙子。但是,您可能需要考虑+,因为在某些情况下,将a添加Word8到true Int确实是明智的。


+1,自从我第一次阅读它以来,我就一直在尝试掌握它,这很有帮助。这种范例是否使无法在(==) :: Int -> Float -> Bool任何地方定义?(当然,不管这是一个好主意)
自我说明-想想一个名字2010年

如果允许多参数类型类(Haskell支持作为扩展),则可以。我用一个例子更新了答案。
乔伊·亚当斯

嗯,有趣。因此,基本上,class Eq a ...将翻译为伪C族interface Eq<A> {bool operator==(A x, A y);},而不是使用模板化的代码比较任意对象,而是使用此“接口”。那正确吗?
请注意-想起

对。您可能还想快速了解Go中的接口。也就是说,您不必将类型声明为实现接口,而只需实现该接口的所有方法。
乔伊·亚当斯

1
@ Notetoself-thinkofaname:关于其他运算符-是和否。它允许==使用不同的名称空间,但不允许覆盖它。请注意,Prelude默认情况下仅包含一个名称空间(),但您可以通过使用扩展名或通过显式导入来阻止加载它(import Prelude ()不会从中导入任何内容Preludeimport qualified Prelude as P也不会在当前名称空间中插入符号)。
Maciej Piechotka

4

允许函数重载,您不能使用可选参数执行以下操作(或者,如果不能,则不能很好地做到)。

琐碎的例子假定没有 base.ToString() 方法

string ToString(int i) {}
string ToString(double d) {}
string ToString(DateTime d) {}
...

对于强类型语言,可以。对于弱类型语言,不可以。您只能使用弱类型语言中的一个函数来完成上述操作。
spex

2

我总是比函数重载更喜欢默认参数。重载的函数通常只调用带有默认参数的“默认”版本。为什么写

int indexOf(char ch)
{
  return self.indexOf(ch, 0);
}

int indexOf(char ch, int fromIndex)
{
  // Do whatever
}

当我可以做的时候:

int indexOf(char ch, int fromIndex=0)
{
  // Do whatever
}

这就是说,我知道有时重载函数做不同的事情,而不是仅仅调用带有默认参数的另一个变种...但在这种情况下,它不是一个坏主意(事实上,它可能是一个主意),只是给它一个不同的名字。

(此外,Python样式的关键字参数可以很好地与默认参数一起使用。)


好吧,让我们再试一次,这次我会尝试... Array Slice(int start, int length) {...}过载了Array Slice(int start) {return this.Slice(start, this.Count - start);}怎么办?无法使用默认参数进行编码。您认为应该给他们起不同的名字吗?如果是这样,您将如何命名?
注意事项-想起

但这并不能解决过载的所有用途。
MetalMikester 2010年

@MetalMikester:你觉得我错过了什么?
mipadi 2010年

@mipadi它错过了列表上的indexOf(char ch)+之类的东西indexOf(Date dt)。我也喜欢默认值,但是它们不能与静态类型互换。
标记

2

您刚刚描述了Java。或C#。

你为什么要重新发明轮子?

确保返回类型是方法签名的一部分,并重载到您的内心内容中,当您不必说时,它确实清除了代码。

function getThisFirstWay(int type)
{ ... }
function getThisSecondWay(int type, double limit)
{ ... }
function getThisThirdWay(int type, String match)
{ ... }

7
用我知道的任何语言,都有一个原因为什么返回类型不是方法签名的一部分,或者至少不是可用于重载解析的一部分。当您将函数作为过程调用而没有将结果分配给变量或属性时,如果所有其他参数都相同,则编译器应如何确定要调用哪个版本?
梅森惠勒2010年

@Mason:您可以根据期望返回的内容试探性地检测期望的返回类型,但是我不希望这样做。
乔什·K

1
嗯...当您不期望任何返回值时,您的启发式方法如何知道期望返回什么?
梅森惠勒

1
类型期望启发式实际上已经到位...您可以执行EnumX.Flag1 | Flag2 | Flag3。不过,我不会实现这一点。如果我这样做了,并且未使用返回类型,那么我将寻找一个的返回类型void
注意:想起名字

1
@梅森:这是一个很好的问题,但在那种情况下,我会寻找一个空函数(如上所述)。同样在理论上,你可以选择其中任何一个,因为它们都执行相同的功能,只是以不同的格式返回数据。
乔什·K

2

Grrr ..还没有足够的特权发表评论..

@Mason Wheeler:请注意Ada,它确实会在返回类型上超载。同样,我的语言Felix在某些情况下也可以做到这一点,特别是当一个函数返回另一个函数并且有这样的调用时:

f a b  // application is left assoc: (f a) b

b的类型可以用于重载解析。在某些情况下,C ++也会在返回类型上重载:

int (*f)(int) = g; // choses g based on type, not just signature

实际上,存在使用类型推断在返回类型上进行重载的算法。使用机器实际上并不难,但问题是人们发现它很困难。(我认为轮廓在《龙书》中已给出,如果我没记错的话,该算法称为跷跷板算法)


2

防止实现函数重载的用例:25个名称相同的方法,它们执行相同的操作,但是在各种模式下具有完全不同的参数集。

防止未实现函数重载的用例:5个名称相似的方法,它们的类型集非常相似,且模式完全相同。

归根结底,我不希望阅读这两种情况下生成的API的文档。

但在一种情况下,它与用户可能会做什么有关。在另一种情况下,由于语言限制,这是用户必须执行的操作。IMO,最好至少允许程序作者足够聪明地明智地超载而不产生歧义的可能性。当您拍打他们的手并拿走选择权时,您基本上就保证了歧义的发生。我更多地是要相信用户做正确的事,而不是假设他们总是做错事。根据我的经验,保护主义往往导致语言社区的行为更糟​​。


1

我选择用我的语言Felix提供普通的重载和多类型类型类。

我认为(开放式)重载是必不可少的,尤其是在使用大量数字类型的语言中(Felix具有所有C的数字类型)。然而,与C ++通过使模板依赖重载来滥用重载不同,Felix多态性是参数化的:您需要对C ++中的模板重载,因为C ++中的模板设计不良。

Felix也提供类型类。对于那些了解C ++但不讨厌Haskell的人,请忽略那些将其描述为重载的人。这远不像重载,而是像模板专门化:您声明一个您不实现的模板,然后根据需要为特定情况提供实现。类型化是参数化的多态性,其实现是通过即席实例化实现的,但并非一定要不受约束:它必须实现预期的语义。

在Haskell(和C ++)中,您无法陈述语义。在C ++中,“概念”概念大致是一种近似语义的尝试。在Felix中,可以使用公理,约简,引理和定理来近似意图。

Felix这样的原理很好的语言中,(开放)重载的主要且唯一的优点是,它对于程序编写者和代码检查者而言,都更容易记住库函数名称。

重载的主要缺点是实现它所需的复杂算法。它也不能很好地进行类型推断:尽管两者并不是完全排斥的,但同时实现两者的算法非常复杂,程序员可能无法预测结果。

在C ++中,这也是一个问题,因为它具有草率的匹配算法并且还支持自动类型转换:在Felix中,我通过要求完全匹配且没有自动类型转换来“解决”此问题。

因此,我认为您可以选择:重载或类型推断。推理虽然很可爱,但是以正确诊断冲突的方式来实施也非常困难。例如,Ocaml会告诉您它在哪里检测到冲突,但不会告诉您从何处推断出预期的类型。

即使您有一个高质量的编译器试图告诉您所有候选者,重载也不会好很多,如果候选者是多态的,则很难看清,如果是C ++模板黑客,那就更糟了。


听起来不错。我想阅读更多,但Felix网页上的文档链接已断开。
注意事项-想起一个名字,2010年

是的,整个站点目前仍在建设中(抱歉)。
Yttrill 2010年

0

取决于上下文,但是我认为当我使用别人编写的类时,重载会使类更有用。您通常会得到较少的冗余。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.