方法重载除了语法糖之外吗?[关闭]


19

方法重载是一种多态吗?对我来说,似乎只是名称相同和参数不同的方法的区别。因此stuff(Thing t)stuff(Thing t, int n)就编译器和运行时而言,它们是完全不同的方法。

它在调用方方面产生了一种幻觉,即对相同类型的对象(即多态)的作用不同。但这只是一种幻想,因为实际上stuff(Thing t)stuff(Thing t, int n)完全不同。

方法重载除了语法糖之外吗?我想念什么吗?


语法糖的一个常见定义是它纯粹是局部的。意思是将一段代码更改为“加糖的”等效代码,反之亦然,这涉及不影响程序整体结构的局部更改。而且我认为方法重载完全符合此标准。让我们看一个示例来演示:

考虑一个类:

class Reader {
    public String read(Book b){
        // .. translate the book to text
    }
    public String read(File b){
        // .. translate the file to text
    }
}

现在考虑使用该类的另一个类:

/* might not be the best example */
class FileProcessor {
    Reader reader = new Reader();
    public void process(File file){
        String text = reader.read(file);
        // .. do stuff with the text
    }
}

好的。现在,让我们看一下用常规方法替换方法重载时需要更改的内容:

更改为和的read方法。仅需更改其名称即可。ReaderreadBook(Book)readFile(file)

中的调用代码FileProcessor略有reader.read(file)变化:变为reader.readFile(file)

就是这样。

如您所见,使用方法重载与不使用方法重载之间的区别纯粹是局部的。这就是为什么我认为它可以视为纯语法糖。

如果您有异议,我想听听您的反对,也许我遗漏了一些东西。


48
最后,任何编程语言功能都只是原始汇编程序的语法糖。
菲利普

31
@Philipp:对不起,但这是一个非常愚蠢的声明。编程语言是从语义而不是语法中获得其用处的。诸如类型系统之类的功能为您提供实际的保证,即使它们实际上可能需要您编写更多内容
back2dos

3
问问自己:运算符重载只是语法糖吗?无论您对这个问题的回答是正确的,还是您提出的问题的答案;)
back2dos

5
@ back2dos:完全同意。我经常阅读这样的句子:“一切都只是汇编程序的语法糖”,这显然是错误的。语法糖是一些不添加任何新语义的现有语法的替代(可能更好)的语法。
乔治(Giorgio)

6
@乔治:对!Matthias Felleisen具有里程碑意义的关于表达的论文有一个精确的定义。基本上:语法糖纯粹是本地的。如果您必须更改程序的全局结构以删除语言功能的使用,则它不是语法糖。也就是说,在汇编器中重写多态OO代码通常涉及添加全局调度逻辑,而这并非纯粹是局部的,因此OO 并不是 “汇编程序的语法糖”。
约尔格W¯¯米塔格

Answers:


29

为了回答这个问题,您首先需要定义“语法糖”。我将使用Wikipedia的

在计算机科学中,语法糖是一种编程语言中的语法,旨在使事物更易于阅读或表达。它使该语言成为人类使用的“更甜”的语言:事物可以更清晰,更简洁地表达,或者以某些人可能喜欢的替代方式表达。

[...]

具体来说,如果可以从语言中删除该构造而不对语言的功能产生任何影响,则将其称为语法糖。

因此,在此定义下,诸如Java的varargs或Scala的for-comprehension之类的功能就是语法糖:它们转换为基础语言功能(在第一种情况下为数组,在第二种情况下为对map / flatmap / filter的调用),将其删除不要改变您可以用语言做的事情。

但是,方法重载在此定义下并不是语法糖,因为删除它会从根本上改变语言(您将不再能够根据参数将其分配给不同的行为)。

的确,只要您有某种方法可以访问方法的参数,就可以模拟方法重载,并且可以根据给定的参数使用“ if”构造。但是,如果您考虑使用这种语法糖,那么您就必须考虑使用图灵机之上的任何东西来同样地成为语法糖。


22
消除重载不会改变语言的功能。您仍然可以做与以前完全相同的事情;您只需要重命名一些方法。这比减少重复的循环要微不足道。
2014年

9
正如我所说,您可以采用所有语言(包括机器语言)只是图灵机之上的语法糖的方法。
2014年

9
如我所见,方法重载可以让您做sum(numbersArray)and sum(numbersList)而不是sumArray(numbersArray)and sumList(numbersList)。我同意多瓦尔(Doval)的观点,它似乎只是一种合成糖。
阿维夫·科恩

3
大多数语言。尝试实现instanceof,类,继承,接口,泛型,反射或使用访问说明ifwhile以及布尔运算,具有完全相同的语义。没有极端情况。请注意,我并不是要挑战您计算与这些结构的特定用途相同的东西。我已经知道您可以使用布尔逻辑和分支/循环来计算任何东西。我要求您实现这些语言功能的语义的完美副本,包括它们提供的任何静态保证(编译时检查仍必须在编译时进行。)
Doval 2014年

6
@ Doval,kdgregory:为了定义语法糖,您必须相对于某些语义来定义它。如果您仅有的语义是“该程序将计算什么?”,那么很显然,所有内容都只是图灵机的语法糖。另一方面,如果您具有可以谈论对象和对象上的某些操作的语义,那么即使语言仍然可以是图灵完备的,但是删除某些语法也将使您无法再表达这些操作。
Giorgio 2014年

13

术语句法糖通常是指特征是由取代定义的情况。该语言没有定义功能的作用,而是定义它与其他功能完全等效。例如,for-each循环

for(Object alpha: alphas) {
}

成为:

for(Iterator<Object> iter = alpha.iterator(); iter.hasNext()) {
   alpha = iter.next();
}

或者采用带有可变参数的函数:

void foo(int... args);

foo(3, 4, 5);

变成:

void Foo(int[] args);

foo(new int[]{3, 4, 5});

因此,可以使用其他语法来代替语法来实现该功能。

让我们看一下方法重载。

void foo(int a);
void foo(double b);

foo(4.5);

可以重写为:

void foo_int(int a);
void foo_double(double b);

foo_double(4.5);

但这并不等同于此。在Java模型中,这是不同的。foo(int a)没有实现foo_int要创建的功能。Java不会通过给歧义函数提供有趣的名称来实现方法重载。要算作语法糖,JAVA将不得不假装你真的写foo_intfoo_double功能,但事实并非如此。


2
我认为没有人说过语法糖的转换必须是微不足道的。即使是这样,我也认为它But, the transformation isn't trivial. At the least, you have to determine the types of the parameters.很粗略,因为不需要确定类型。它们在编译时是已知的。
Doval

3
“要算作语法糖,Java必须假装您确实编写了foo_int和foo_double函数,但事实并非如此。” -只要我们谈论重载方法而不是多态,foo(int)/ foo(double)foo_int/ 之间会有什么区别foo_double?我不太了解Java,但是我可以想象这样的重命名确实会在JVM中发生(嗯-可能使用foo(args)而不是foo_args-至少在C ++中使用符号修饰(确实-符号修饰从技术上讲是实现细节,而不是一部分)语言))
Maciej Piechotka,2014年

2
@Doval:“我认为没有人说过语法糖转换必须是微不足道的。” –是的,但是必须是本地的。我知道的语法糖的唯一有用定义是来自Matthias Felleisen关于语言表达能力的著名论文,基本上它说如果您可以重新编写用L + y语言编写的程序(即具有某些y功能的L语言),语言L(即没有功能y的该语言的子集)而不更改程序的全局结构(即仅进行局部更改),则yL + y中的语法糖,并且
JörgW Mittag 2014年

2
…不增加L的表现力。但是,如果您不能做到这一点,即您必须对程序的全局结构进行更改,那么它就不是语法糖,实际上确实使L + yL更具表现力。例如,具有增强for循环的Java不会比没有Java的Java具有更高的表现力。(我会说,它更好,更简洁,更易读,并且周围都有更好的表现,但没有更多表现力。)不过,我不确定重载情况。我可能必须重新阅读本文才能确定。我的直觉说这句法糖,但我不确定。
JörgW Mittag 2014年

2
@MaciejPiechotka,如果它是语言定义的一部分,那么函数被如此重命名,并且您可以使用这些名称访问该函数,我认为这将是语法糖。但是由于隐藏了它的实现细节,我认为这使它失去了语法糖的资格。
2014年

8

鉴于名称改写有效,它不是语法语法糖吗?

它使调用者可以想象自己在调用相同的函数,而实际上并非如此。但是他可以知道他所有职能的真实姓名。只有通过将无类型的变量传递给有类型的函数并建立其类型以使调用可以根据名称转到正确的版本来实现延迟多态性,这才是真正的语言功能。

不幸的是,我从未见过任何语言能做到这一点。当有歧义时,这些编译器不会解决它,他们坚持要求作者为他们解决它。


您在此处寻找的功能称为“多次派发”。许多语言都支持它,包括Haskell,Scala和(自4.0起)C#。
伊恩·加洛韦

我想将类的参数与直接方法重载分开。在直接方法重载的情况下,程序员编写所有版本,编译器只知道如何选择一个。那只是语法糖,甚至通过多次调度也可以通过简单的名称处理来解决。---在类中存在参数时,编译器将根据需要生成代码,并且将其完全更改。
乔恩·杰伊·奥伯马克2014年

2
我想你误会了。例如,在C#中,如果方法的参数之一是重载解析,则在运行时而不是在编译时dynamic进行重载解析。这就是多重​​分派,不能通过重命名功能来复制。
伊恩·加洛韦

挺酷的。但是,我仍然可以测试变量类型,因此这仍然只是语法糖上的内置函数。这是一种语言功能,但几乎没有。
乔恩·杰伊·奥伯马克2014年

7

根据语言的不同,它是否是语法糖。

例如,在C ++中,您可以使用重载和模板来做事,而这不会造成麻烦(手动编写模板的所有实例化或添加很多模板参数)。

请注意,动态分派是一种重载形式,可以对某些参数进行动态解析(对于某些语言,只有一种特殊的语言可以使用this,但并非所有语言都受此限制),因此我不称这种形式的重载语法糖。


当基本不正确时,我不确定其他答案的效果如何。
Telastyn 2014年

5

对于当代语言,它只是语法糖。以完全与语言无关的方式,不仅如此。

以前,此答案仅说明它不仅仅是语法糖,但是如果您在注释中看到,Falco提出了一个观点,即现代语言似乎都消失了,这是一个谜。他们不会将方法重载与动态确定在同一步骤中调用哪个函数混合在一起。稍后将对此进行澄清。

这就是为什么它应该更多的原因。

考虑一种同时支持方法重载和无类型变量的语言。您可以具有以下方法原型:

bool someFunction(int arg);

bool someFunction(string arg);

在某些语言中,您可能会被屈服于在编译时知道给定的代码行将调用其中之一。但是在某些语言中,并非所有变量都被键入(或者它们都隐式地键入为as Object或其他类型),因此,请想象构建一个字典,其键映射到不同类型的值:

dict roomNumber; // some hotels use numbers, some use letters, and some use
                 // alphanumerical strings.  In some languages, built-in dictionary
                 // types automatically use untyped values for their keys to map to,
                 // so it makes more sense then to allow for both ints and strings in
                 // your code.

现在,如果您想申请someFunction这些房间号之一怎么办?您称之为:

someFunction(roomNumber[someSortOfKey]);

someFunction(int)称为还是被someFunction(string)称为?在这里,您将看到一个示例,其中这些方法并非完全正交,尤其是在高级语言中。该语言必须在运行时确定要调用其中之一,因此它仍必须至少将它们视为相同的方法。

为什么不简单地使用模板?为什么不简单地使用无类型的参数呢?

灵活性和更细粒度的控制。有时使用模板/无类型参数是一种更好的方法,但有时并非如此。

您必须考虑以下情况:例如,您可能有两个方法签名,每个方法签名都带有an int和a string作为参数,但是每个签名的顺序不同。您可能有充分的理由执行此操作,因为每个签名的实现可能在很大程度上做相同的事情,但是只是略有不同。例如,日志记录可能不同。或者,即使它们执行相同的操作,您也可以仅从指定参数的顺序中自动收集某些信息。从技术上讲,您可以只使用伪开关语句来确定传入的每个参数的类型,但这很麻烦。

那么下一个例子是不好的编程习惯吗?

bool stringIsTrue(int arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool stringIsTrue(Object arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool stringIsTrue(string arg)
{
    if (arg == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

是的,总的来说。在这个特定的示例中,它可以使某人避免尝试将其应用于某些原始类型并恢复意外行为(这可能是一件好事)。但是,假设我缩写了上面的代码,并且实际上,您对所有基本类型和Objects 都有重载。那么下面的代码确实更合适:

bool stringIsTrue(untyped arg)
{
    if (arg.toString() == "0")
    {
        return false;
    }
    else
    {
        return true;
    }
}

但是,如果仅需要将其用于ints和strings,又希望它根据相应的更简单或更复杂的条件返回true,该怎么办?那么您就有充分的理由使用重载:

bool appearsToBeFirstFloor(int arg)
{
    if (arg.digitAt(0) == 1)
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool appearsToBeFirstFloor(string arg)
{
    string firstCharacter = arg.characterAt(0);
    if (firstCharacter.isDigit())
    {
        return appearsToBeFirstFloor(int(firstCharacter));
    }
    else if (firstCharacter.toUpper() == "A")
    {
        return true;
    }
    else
    {
        return false;
    }
}

但是,为什么不只给这些函数两个不同的名称呢?您仍然具有相同数量的细粒度控制,不是吗?

因为,如前所述,有些酒店使用数字,有些使用字母,有些使用数字和字母的混合体:

appearsToBeFirstFloor(roomNumber[someSortOfKey]);

// will treat ints and strings differently, without you having to write extra code
// every single spot where the function is being called

这仍然与我在现实生活中使用的代码并不完全相同,但是它应该可以说明我的观点。

但是...这就是为什么它不只是当代语言中的语法糖。

Falco在评论中指出,当前的语言基本上不会在同一步骤中混合使用方法重载和动态函数选择。我以前理解某些语言的工作方式是,可以appearsToBeFirstFloor在上面的示例中重载,然后该语言将在运行时根据未类型化变量的运行时间值确定要调用的函数版本。造成这种混乱的部分原因是使用ECMA等多种语言(例如ActionScript 3.0),您可以在其中轻松随机地在运行时在特定代码行上调用哪个函数。

如您所知,ActionScript 3不支持方法重载。对于VB.NET,可以在不显式分配类型的情况下声明和设置变量,但是当您尝试将这些变量作为参数传递给重载方法时,它仍然不希望读取运行时值来确定要调用的方法。相反,它希望找到带有类型Object或无类型参数或类似参数的方法。因此,上面的intvs. string示例也无法在该语言中使用。C ++存在类似的问题,因为当您使用诸如void指针之类的东西或类似的其他机制时,它仍然需要您在编译时手动消除类型的歧义。

因此,正如第一个标题所说的...

对于当代语言,它只是语法糖。以完全与语言无关的方式,不仅如此。像上面的示例一样,使方法重载更加有用和相关,实际上可能是添加到现有语言中的一个不错的功能(正如AS3广泛要求的那样),或者它还可以用作许多不同的基本支柱之一。创建新的过程/面向对象语言。


3
您能否命名在运行时而非编译时真正处理Function-Dispatch的任何语言?我知道的所有语言都要求在编译时确定哪个函数被称为...
Falco 2014年

@Falco ActionScript 3.0在运行时对其进行处理。你可以,例如,使用一个函数,随机三根弦的回报之一,然后用它的返回值来调用三种功能中的任何一个随机:this[chooseFunctionNameAtRandom](); 如果chooseFunctionNameAtRandom()返回或者"punch""kick"或者"dodge",那么你就可以正是如此实现一个非常简单的随机元素,例如Flash游戏中敌人的AI。
Panzercrisis

1
是的-但是它们都是获取动态函数分配的真正语义方法,Java也有这些方法。但是它们与重载不同,重载是静态的,只是语法糖,而动态分派和继承是真实的语言功能,它们提供了新功能!
Falco 2014年

1
...我还尝试了C ++中的void指针以及基类指针,但是编译器希望我自己先消除歧义,然后再将其传递给函数。所以现在我想知道是否删除此答案。看起来语言似乎几乎总是在同一条指令或语句中就将动态函数选择与函数重载结合起来,但是在最后一秒就走了。但是,这将是一个不错的语言功能。也许有人需要制作一种具有这种语言的语言。
装甲危机

1
让答案留下来,也许考虑从答案中的评论中包括您的一些研究?
法尔科

2

这实际上取决于您对“语法糖”的定义。我将尝试解决我想到的一些定义:

  1. 当使用某个功能的程序始终可以在不使用该功能的另一个程序中转换时,该功能就是语法糖。

    在这里,我们假设存在一组无法翻译的原始特征:换句话说,没有“使用特征Y可以替换特征X”和“可以将特征X替换特征Y”的循环。如果两者中的一个为真,则另一个特征可以用不是第一个特征或原始特征来表示。

  2. 与定义1相同,但是额外的要求是翻译后的程序必须与第一个一样具有类型安全性,即,通过重复使用,您不会丢失任何类型的信息。

  3. OP的定义:如果某个功能的翻译不改变程序的结构,而仅需要“本地更改”,则该功能就是语法糖。

让我们以Haskell为例进行重载。Haskell通过类型类提供用户定义的重载。例如,+*操作是在Num类型类中定义的,任何具有此类的(完整)实例的类型都可以与一起使用+。例如:

instance Num a => Num (b, a) where
    (x, y) + (_, y') = (x, y + y')
    -- other definitions

("Hello", 1) + ("World", 3) -- -> ("Hello", 4)

关于Haskell的类型类的一件众所周知的事情是,您可以摆脱它们。也就是说,您可以将使用类型类的任何程序转换为不使用类型类的程序。

翻译非常简单:

  • 给定一个类定义:

    class (P_1 a, ..., P_n a) => X a where
        op_1 :: t_1   ... op_m :: t_m
    

    您可以将其转换为代数数据类型:

    data X a = X {
        X_P_1 :: P_1 a, ... X_P_n :: P_n a,
        X_op_1 :: t_1, ..., X_op_m :: t_m
    }
    

    这里X_P_iX_op_i选择。即给定X a适用X_P_1于该值的类型的值将返回存储在该字段中的值,因此它们是具有类型的函数X a -> P_i a(或X a -> t_i)的。

    对于一个非常粗略的 anology你能想到的类型的值X a作为struct秒,然后如果x是类型的X a表达式:

    X_P_1 x
    X_op_1 x
    

    可以看作是:

    x.X_P_1
    x.X_op_1
    

    (仅使用位置字段而不是命名字段很容易,但是在示例中命名字段更易于处理,并且避免使用一些样板代码)。

  • 给定实例声明:

    instance (C_1 a_1, ..., C_n a_n) => X (T a_1 ... a_n) where
        op_1 = ...; ...;  op_m = ...
    

    您可以将其转换为给定C_1 a_1, ..., C_n a_n类字典的函数,该函数返回该类型的字典值(即type的值X aT a_1 ... a_n

    换句话说,以上实例可以转换为类似的功能:

    f :: C_1 a_1 -> ... -> C_n a_n -> X (T a_1 ... a_n)
    

    (请注意,n可能是0)。

    实际上,我们可以将其定义为:

    f c1 ... cN = X {X_P_1=get_P_1_T, X_P_n=get_P_n_T,
                     X_op_1=op_1, ..., X_op_m=op_m}
        where
            op_1 = ...
            ...
            op_m = ...
    

    在哪里op_1 = ...可以op_m = ...找到在instance声明中找到的定义,并且get_P_i_T是由P_iT类型的实例定义的函数(这些必须存在,因为P_is是的超类X)。

  • 给定重载函数的调用:

    add :: Num a => a -> a -> a
    add x y = x + y
    

    我们可以显式地传递相对于类约束的字典,并获得等效调用:

    add :: Num a -> a -> a -> a
    add dictNum x y = ((+) dictNum) x y
    

    请注意,类约束如何简单地成为一个新的论点。在+如前面所解释的在变换后程序是选择器。换句话说add,在给定翻译函数的参数类型的字典的情况下,该函数将首先“解包”实际函数以使用计算结果(+) dictNum,然后将该函数应用于参数。

这只是关于整个过程的快速草图。如果您有兴趣,请阅读Simon Peyton Jones等的文章。

我相信类似的方法可以可以用于其他语言的重载。

但是,这表明,如果您对语法糖的定义为(1),则过载就是语法糖。因为您可以摆脱它。

但是,翻译后的程序会丢失一些有关原始程序的信息。例如,它不强制父类的实例存在。(即使提取父级字典的操作仍必须是该类型,您也可以传入undefined或其他多态值,这样就可以在不构建的值的X y情况下构建的值P_i y,因此翻译不会丢失所有类型安全性)。因此,它不是根据(2)的合成糖

至于(3)。我不知道答案应该是是还是否。

我之所以说不,是因为,例如,实例声明成为函数定义。重载的函数会获得一个新参数(这意味着它会同时更改定义和所有调用)。

我会说是的,因为这两个程序仍然是一对一的映射,因此“结构”实际上并没有改变太多。


也就是说,我要说的是,重载所带来的实用优势是如此之大,以至于使用“贬义”一词(例如“语法糖”)似乎并不正确。

您可以将所有Haskell语法翻译成一种非常简单的Core语言(实际上是在编译时完成的),因此对于大多数只是lambda微积分加一些新结构的东西,大多数 Haskell语法可以被视为“语法糖”。但是,我们可以同意,Haskell程序更易于处理且非常简洁,而翻译后的程序则很难阅读或思考。


2

如果调度是在编译时解决的,仅取决于参数表达式的静态类型,则可以肯定地认为,只要程序员“知道”静态类型,并且使用不同的名称替换两种不同的方法,这就是“语法糖”。可以使用正确的方法名称代替重载名称。它也是静态多态性的一种形式,但是以这种有限的形式,它通常不是很强大。

当然,无论何时更改变量类型,都必须更改调用的方法的名称,这很麻烦,但是例如在C语言中,它被认为是可管理的麻烦,因此C不会有函数重载(尽管现在确实具有通用宏)。

在C ++模板中,以及在执行非平凡的静态类型推导的任何语言中,除非您还认为静态类型推导是“语法的推论”,否则您不能真正辩称这是“语法糖”。没有模板将是一件令人讨厌的事情,在C ++的上下文中,这将是“无法管理的麻烦”,因为它们对于语言及其标准库是如此地习惯。因此,在C ++中,它不仅仅是一个好帮手,它对语言的风格也很重要,因此,我认为您必须称其为“语法糖”。

在Java中,考虑到例如PrintStream.print和有多少个重载,您可能不仅仅考虑方便PrintStream.println。但是,DataInputStream.readX由于Java不会在返回类型上重载,因此存在许多方法,因此从某种意义上讲,这只是为了方便。这些都是针对原始类型的。

我不记得在Java中会发生什么,如果我有类AB延伸O,我重载方法foo(O)foo(A)并且foo(B),然后在一个通用的与<T extends O>我所说的foo(t)地方t就是一个实例T。在该情况下TA我得到一个基于超载调度或者是它,好像我打电话foo(O)

如果是前者,则Java方法重载比糖更好,就像C ++重载一样。使用您的定义,我想我可以在Java中本地编写一系列类型检查(这很脆弱,因为新的重载foo将需要附加检查)。除了接受这种脆弱性之外,我无法在调用站点进行本地更改以使其正确无误,相反,我不得不放弃编写通用代码。我认为防止preventing肿的代码可能是语法上的糖,但防止易碎的代码还不止这些。因此,静态多态性通常不仅仅是语法糖。特定语言的情况可能有所不同,具体取决于该语言允许您通过“不知道”静态类型而获得的距离。


在Java中,重载在编译时解决。鉴于使用了类型擦除,否则它们将是不可能的。此外,即使没有类型擦除,如果T:Animal是被式SiameseCat和现有的过载是Cat Foo(Animal)SiameseCat Foo(Cat),和Animal Foo(SiameseCat),其中过载,应选择是否TSiameseCat
2014年

@supercat:很有意义。因此,我本可以在不记得(或当然可以运行)的情况下找到答案的。因此,与C ++重载与通用代码相关的方式相同,Java重载并不比Sugar好。仍然有其他方法可以使它们比局部转换更好。我不知道应该将示例更改为C ++,还是将其保留为不是真正的Java的Java。
史蒂夫·杰索普

在方法具有可选参数的情况下,重载可能会有所帮助,但它们也很危险。假设将行long foo=Math.round(bar*1.0001)*5更改为long foo=Math.round(bar)*5。如果bar等于123456789L,将如何影响语义?
2014年

@supercat我认为真正的危险是存在从long到的隐式转换double
2014年

@Doval:要double
2014年

1

看起来“语法糖”听起来像贬义词,就像无用或轻浮。这就是为什么这个问题会引发许多否定答案的原因。

但是您是对的,方法重载不会为语言添加任何功能,除了可以对不同的方法使用相同的名称。您可以使参数类型明确,程序仍将相同。

包名称也是如此。字符串只是java.lang.String的语法糖。

实际上,类似

void fun(int i, String c);

类MyClass中的名称应类似于“ my_package_MyClass_fun_int_java_lang_String”。这将唯一地标识该方法。(JVM在内部执行类似的操作)。但是你不想写那个。这就是为什么编译器将允许您编写fun(1,“ one”)并确定它是哪种方法的原因。

但是,重载可以做一件事:如果重载具有相同数量参数的方法,则编译器会自动找出哪个版本最适合由匹配参数给出的参数,不仅具有相等的类型,还包括给定参数是已声明参数的子类。

如果您有两个重载的程序

addParameter(String name, Object value);
addParameter(String name, Date value);

您不需要知道日期程序的特定版本。addParameter(“ hello”,“ world)将调用第一个版本,addParameter(” now“,new Date())将调用第二个版本。

当然,您应该避免将一个方法重载为另一个功能完全不同的方法。


1

有趣的是,这个问题的答案将取决于语言。

特别是,重载通用编程(*)之间存在相互作用,并且取决于通用编程的实现方式,它可能只是语法糖(Rust)或绝对必要(C ++)。

也就是说,当使用显式接口实现通用编程时(在Rust或Haskell中,这些接口将是类型类),那么重载只是语法糖。甚至实际上可能不是该语言的一部分。

另一方面,当通用编程通过鸭式输入(动态或静态)实现时,方法的名称是必不可少的协定,因此重载对于系统正常工作是必不可少的。

(*)一次编写一种方法,用于以统一的方式对各种类型进行操作。


0

在某些语言中,毫无疑问仅仅是语法糖。但是,它的作用取决于您的观点。我将把这个讨论留在以后的答案中。

现在,我只想指出,在某些语言中,它肯定不是语法糖。至少在不要求您使用完全不同的逻辑/算法来实现同一事物的情况下。这就像声称递归是语法糖(之所以这样,是因为您可以使用循环和堆栈编写所有递归算法)。

具有讽刺意味的是,这种语言很难将其替换为“功能重载”。而是将其称为“模式匹配”(可以将其视为重载的超集,因为我们不仅可以重载类型,还可以重载值)。

这是Haskell中斐波那契函数的经典朴素实现:

fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

可以说,这三个功能可以用if / else代替,因为它通常以任何其他语言完成。但这从根本上做出了非常简单的定义:

fib n = fib (n-1) + fib (n-2)

更混乱,并且不直接表达斐波那契数列的数学概念。

因此,有时,如果唯一的用途是允许您使用不同的参数调用函数,则可能是语法糖。但是有时候它比这更根本。


现在讨论什么运算符重载可能是一个糖。您已经确定了一个用例-可用于实现带有不同参数的相似功能。所以:

function print (string x) { stdout.print(x) };
function print (number x) { stdout.print(x.toString) };

可以替代地实现为:

function printString (string x) {...}
function printNumber (number x) {...}

甚至:

function print (auto x) {
    if (x instanceof String) {...}
    if (x instanceof Number) {...}
}

但是,运算符重载对于实现可选参数也是一种糖(某些语言具有运算符重载,但没有可选参数):

function print (string x) {...}
function print (string x, stream io) {...}

可用于实现:

function print (string x, stream io=stdout) {...}

使用这种语言(谷歌“ Ferite语言”),删除运算符重载会彻底删除一项功能-可选参数。授予使用这两种功能(c ++)删除一个或另一个功能的语言不会产生任何实际效果,因为这两个功能均可用于实现可选参数。


Haskell是一个很好的例子,说明了为什么运算符重载不是语法糖,但是我认为一个更好的例子是使用模式匹配来解构代数数据类型(据我所知,如果没有模式匹配,这是不可能的)。
2014年

@ 11684:您能举个例子吗?老实说,我完全不了解Haskell,但是当我看到那个fib示例(在youtube上的computerphile上)时,发现它的模式匹配非常优雅。
slebetman 2014年

给定类似的数据类型,data PaymentInfo = CashOnDelivery | Adress String | UserInvoice CustomerInfo您可以在类型构造函数上进行模式匹配。
2014年

像这样:getPayment :: PaymentInfo -> a getPayment CashOnDelivery = error "Should have been paid already" getPayment (Adress addr) = -- code to notify administration to send a bill getPayment (UserInvoice cust) = --whatever. I took the data type from a Haskell tutorial and have no idea what an invoice is。我希望这一评论是可以理解的。
2014年

0

我认为这在大多数语言中都是简单的语法糖(至少我所知道的...),因为它们都需要在编译时进行明确的Function-call。而且,编译器只需用指向正确实现签名的显式指针替换函数调用即可。

Java范例:

String s; int i;
mangle(s);  // Translates to CALL ('mangle':LString):(s)
mangle(i);  // Translates to CALL ('mangle':Lint):(i)

因此,最后可以用一个简单的带有搜索和替换功能的编译器宏完全替换,用mangle_String替换重载的mangle_mangle_int-因为arguments-list是最终函数标识符的一部分,所以实际上就是这样->和因此,它只是语法糖。

现在,如果有一种语言,实际上是在运行时确定功能的,就像对象中覆盖方法一样,情况就会有所不同。但是我不认为有这种语言,因为method.overloading容易产生歧义,编译器无法解决这种歧义,必须由程序员使用显式强制转换进行处理。这不能在运行时完成。


0

在Java中,类型信息是在其中编译的,调用的重载是在编译时决定的。

以下是sun.misc.UnsafeEclipse的类文件编辑器中的(Atomics实用程序)片段。

  // Method descriptor #143 (Ljava/lang/Object;I)I (deprecated)
  // Stack: 4, Locals: 3
  @java.lang.Deprecated
  public int getInt(java.lang.Object arg0, int arg1);
    0  aload_0 [this]
    1  aload_1 [arg0]
    2  iload_2 [arg1]
    3  i2l
    4  invokevirtual sun.misc.Unsafe.getInt(java.lang.Object, long) : int [231]
    7  ireturn
      Line numbers:
        [pc: 0, line: 213]

如您所见,调用中包含了被调用方法的类型信息(第4行)。

这意味着您可以创建一个接受类型信息的Java编译器。例如,使用这种表示法,上述来源就是:

@Deprecated
public in getInt(Object arg0, int arg1){
     return getInt$(Object,long)(arg0, arg1);
}

而强制转换为long将是可选的。

在其他静态类型的编译语言中,您将看到类似的设置,其中编译器将根据类型决定将调用哪种重载并将其包含在绑定/调用中。

唯一的例外是C动态库,其中不包含类型信息,尝试创建重载函数将导致链接器抱怨。

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.