19

Is method overloading a type of polymorphism? To me it seems like simply the differentiation of methods with the same name and different parameters. So stuff(Thing t) and stuff(Thing t, int n) are entirely different methods as far as the compiler and runtime are concerned.

It creates the illusion, on the caller's side, that it's the same method that acts differently on different kinds of objects - polymorphism. But that's only an illusion, because actually stuff(Thing t) and stuff(Thing t, int n) are completely different methods.

Is method overloading anything more than syntactic sugar? Am I missing something?


A common definition for syntactic sugar, is that it is purely local. Meaning changing a piece of code to its 'sweetened' equivalent, or vice versa, involves local changes that don't affect the overall structure of the program. And I think method overloading precisely fits this criterion. Let's look at an example to demonstrate:

Consider a class:

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

Now consider another class that uses this class:

/* 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
    }
}

Okay. Now let's see what needs to change if we replace the method overloading with regular methods:

The read methods in Reader change to readBook(Book) and readFile(file). Only a matter of changing their names.

The calling code in FileProcessor changes slightly: reader.read(file) changes to reader.readFile(file).

And that's it.

As you can see, the difference between using method overloading and not using it, are purely local. And that's why I think it qualifies as pure syntactic sugar.

I'd like to hear your objections if you have some, maybe I'm missing something.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
Aviv Cohn
  • 21,190
  • 31
  • 118
  • 178
  • 50
    In the end, any programming language feature is just syntactic sugar for raw assembler. – Philipp Aug 25 '14 at 17:43
  • 32
    @Philipp: Sorry, but that's a really stupid statement. Programming languages derive their usefulness from semantics, not syntax. Features like a type system give you actual guarantees, even though they may actually require you to write *more*. – back2dos Aug 25 '14 at 18:12
  • 3
    Ask yourself this: Is operator overloading just syntactic sugar? Whatever answer to that question you hold true is also the answer to the question you asked ;) – back2dos Aug 25 '14 at 18:20
  • @back2dos I agree with you 98%. I think it's too much of a stretch that languages don't derive *any* usefulness from syntax. That's like saying you can invert all the variable names in a program and it wouldn't matter. Semantically, not really, but it'll definitely be harder to understand. But I agree in the semantic differences between languages matter more in most cases. And assembly is just a convenient syntax for machine code. – Doval Aug 25 '14 at 18:25
  • 5
    @back2dos: Totally agree with you. I read the sentence "everything is just syntactic sugar for assembler" way too often, and it is clearly wrong. Syntactic sugar is an alternative (possibly nicer) syntax for some existing syntax that does not add any new semantics. – Giorgio Aug 25 '14 at 18:38
  • 6
    @Giorgio: right! There is a precise definition in Matthias Felleisen's landmark paper on expressivity. Basically: syntactic sugar is purely local. If you have to change the global structure of the program to remove usage of the language feature, then it's not syntactic sugar. I.e. rewriting polymorphic OO code in assembler typically involves adding global dispatch logic, which is *not* purely local, therefore OO is *not* "just syntactic sugar for assembler". – Jörg W Mittag Aug 25 '14 at 19:13
  • @JörgWMittag, how about something like `auto` in c++11? It seems local, but determining the type could require looking at non-local parts of the code. – Winston Ewert Aug 25 '14 at 19:52
  • 3
    @WinstonEwert: Type inference is by definition a type system feature, not a syntactic one. – Jörg W Mittag Aug 25 '14 at 20:11
  • @JörgWMittag, sure the type system is involved, but it seems allowing the formal type to be elided is a syntax issue. – Winston Ewert Aug 25 '14 at 20:26
  • Its clear from the various responses that different programmers have quite different definitions of syntactic sugar. As such, there is no simple answer to the question. It depends on which definition you adopt. – Winston Ewert Aug 25 '14 at 21:49
  • 1
    @back2dos - re operator overloading, there are two parts: one is a straightforward translation of an operator into a function call (eg, `+` becomes `operator+`), and I think that's clearly syntactic sugar. The second part is the overloading of `operator+` to support different types, and, well, my answer shows my position on that. – kdgregory Aug 25 '14 at 22:08
  • @Giorgio which means everything is syntactic sugar for raw machine language as everything ends up as raw machine language eventually. Nothing adds anything to the machine language, it just defines new ways to express that machine language, a new syntax, and thus is "mere syntactic sugar". – jwenting Aug 26 '14 at 04:58
  • @Doval: Well, ok, my statement that no usefulness comes from syntax is a bit radical, but that was mainly to get my point across. Given though that programming languages simply have to have a syntax and while you're at it you might as well make it readable, I think this is really a secondary concern and more of an incidental effect (except for all the code golfing languages out there). Particularly in the past 20 years, when the tooling for writing parsers made the task easy to surmount. – back2dos Aug 26 '14 at 05:27
  • @jwenting: Do you know the difference between syntax and semantics? Do you think in terms of processor registers when writing in Python, or do you think in terms of objects, strings, numbers, and so on? – Giorgio Aug 26 '14 at 06:36
  • I think the question is entirely valid, but since it's concerned with the names and semantics of things rather than the structure of expressions, overloading would properly be called *semantic* sugar, if such a term was current. – Kilian Foth Aug 26 '14 at 08:02
  • Following your reasoning, the overloaded "+"-operator is purely syntactic sugar, because it can add 2 numbers but also concatenate 2 strings. – Pieter B Aug 26 '14 at 10:00
  • BTW: [Funny coincidence](https://i.imgur.com/cPgSmNc.png) :) – user11153 Aug 26 '14 at 13:04
  • This question basically asks "what precisely is meant by 'syntactic sugar'". Nothing to add here that's not already in the Wikipedia definition. – kevin cline Aug 26 '14 at 18:03

12 Answers12

29

To answer this, you first need a definition for "syntactic sugar." I'll go with Wikipedia's:

In computer science, syntactic sugar is syntax within a programming language that is designed to make things easier to read or to express. It makes the language "sweeter" for human use: things can be expressed more clearly, more concisely, or in an alternative style that some may prefer.

[...]

Specifically, a construct in a language is called syntactic sugar if it can be removed from the language without any effect on what the language can do

So, under this definition, features such as Java's varargs or Scala's for-comprehension are syntactic sugar: they translate into underlying language features (an array in the first case, calls to map/flatmap/filter in the second), and removing them would not change the things you can do with the language.

Method overloading, however, is not syntactic sugar under this definition, because removing it would fundamentally change the language (you'd no longer be able to dispatch to distinct behavior based on arguments).

True, you can simulate method overloading as long as you have some way to access the arguments of a method, and can use an "if" construct based on the arguments that you're given. But if you consider that syntactic sugar, you'd have to consider anything above a Turing machine to likewise be syntactic sugar.

kdgregory
  • 5,220
  • 23
  • 27
  • 23
    Removing overloading wouldn't change what the language can do. You could still do exactly the same things as before; you'd just have to rename a few methods. That's a more trivial change than desugaring loops. – Doval Aug 25 '14 at 17:44
  • 9
    As I said, you could take the approach that all languages (including machine languages) are simply syntactic sugar on top of a Turing machine. – kdgregory Aug 25 '14 at 17:55
  • 9
    As I see it method overloading simply lets you do `sum(numbersArray)` and `sum(numbersList)` instead of `sumArray(numbersArray)` and `sumList(numbersList)`. I agree with Doval, it seems like mere syntatic sugar. – Aviv Cohn Aug 25 '14 at 17:59
  • 2
    @kdgregory But that definition is rather rare. The one from Wikipedia is more common, and is clearly what Prog had in mind. It's also a *stronger* (more strict) definition because it limits you to stay within the same language. My point is that method overloading satisfies the Wikipedia definition. – Doval Aug 25 '14 at 18:13
  • 1
    @Doval - other than `if`, `while`, logical AND, and logical NOT, what *isn't* syntactic sugar? – kdgregory Aug 25 '14 at 18:36
  • 3
    Most of the language. Try implementing `instanceof`, classes, inheritance, interfaces, generics, reflection or access specifiers using `if`, `while`, and boolean operators, with *the exact same semantics*. No corner cases. Note that I'm not challenging you to compute the same things as specific uses of those constructs. I already *know* you can compute anything using boolean logic and branching/looping. I'm asking you to implement perfect copies of the semantics of those language features, including any static guarantees they provide (compile time checks must still be done at compile time.) – Doval Aug 25 '14 at 18:48
  • 6
    @Doval, kdgregory: In order to define syntactic sugar you have to define it relative to some semantics. If the only semantics you have is "What does this program compute?", then it is clear that everything is just syntactic sugar for a Turing machine. On the other hand, if you have a semantics in which you can speak about objects and certain operations on them, then removing certain syntax will not allow you to express those operation any more, even though the language could still be Turing-complete. – Giorgio Aug 25 '14 at 19:25
  • kdgregory: To illustrate why I think method overloading is syntatic sugar, le'ts consider an example program with and without method overloading. Say you have a class with two methods: `read(Book)` and `read(File)`. In another class you have code that calls each of these methods: `reader.read(aBook); reader.read(aFile);`. Now we want to change the code to not use method overloading. Let's see what has to be changed: simply renaming methods. `read(Book)` changes to `readBook(Book)` and `read(File)` to `readFile(File)`. **The changes are local. Not a single line of code added or reduced.** – Aviv Cohn Aug 25 '14 at 20:50
  • Please look at the edit to my question. – Aviv Cohn Aug 25 '14 at 20:59
  • "anything above a Turing machine to likewise be syntactic sugar"... well, yeah, exactly. – Mitch Aug 25 '14 at 21:54
  • @Doval - I'm not sure where you're going with this. It seemed that you argued that one could *emulate* overloaded functions by hand-coding mangled names. Which I agree with. But then your examples of class implementation confuse me: a class is simply a collection of data associated with operations; as long as you have a unique identifier for a class, and maintain information about class hierarchy, then of course you can do all of that run-time type examination (as C-Front showed, by translating C++ to C, classes are just syntactic sugar over a struct). – kdgregory Aug 25 '14 at 21:57
  • I'm not sure if I muddied the water by leaving variables (memory-based storage) out of my list of basic building blocks, but they are of course implied by Turing-completeness, and I assumed that you mentally filled in my omission. – kdgregory Aug 25 '14 at 21:58
  • I'm also confused by your comments on compile-time type safety. First, because it assumes a statically-typed language, which doesn't seem to be a requirement. And second, because it seems to diverge from the idea of *emulating* language syntax using *other* syntax from the language. If you're going to consider compiler metadata to be under consideration, then you have to consider *all* metadata, including the basic keywords of the language. In which case, I contend that *nothing* is syntactic sugar. – kdgregory Aug 25 '14 at 22:03
  • @Giorgio - I like that interpretation, although it leads to a pin-dancing debate of "is the operation addition, or is it addition of a double?". – kdgregory Aug 25 '14 at 22:10
  • A type system is not syntactic sugar over assembler. No matter how hard you try, you simply cannot enforce some code you are using has an erroneous type passed to it. Languages have semantics beyond the runtime. – Thomas Eding Aug 26 '14 at 03:19
  • Side note: `if (cond) { foo(); }` is syntactic sugar for `bool hasLooped=false; while (!hasLooped && cond) { hasLooped=true; foo(); }` – Thomas Eding Aug 26 '14 at 03:21
  • 3
    -1 talking about Turing machines is a red herring here, these are two completely different levels of abstraction. A Turing machine and a RAM machine are separate models of computation -- the former is provably `O(n^3)` slower than the latter. If you need to take a cubic slowdown to get things done, it's definitely not syntactic sugar. – Patrick Collins Aug 26 '14 at 04:47
  • @Doval operators can't be simply renamed. – Shadows In Rain Aug 26 '14 at 11:26
  • 1
    @kdgregory It's not about emulation, it's about equivalence. For any two overloaded methods I can rewrite the program by renaming one and I'd get a program with the same overall structure and the exact same semantics. If the original program didn't compile for any reason, neither will the new one. If the original program compiled, so will the new one. You'll have the same behavior at run time. I can mechanically rewrite any use of a `for` loop to a `while` and get an equivalent program; you can't mechanically rewrite class-based code into branches and loops and get the exact same semantics. – Doval Aug 26 '14 at 11:36
  • Meh. Smalltalk is evidence that even "if" and "while" could be considered syntactic sugar, depending on which features you consider "essential". (The language has like 6 keywords, and not one of them is for flow control.) – cHao Aug 26 '14 at 14:21
  • This answer would be correct if method overloading was determined at runtime, and not compile time. Saying that "I can no longer have 2 different methods with the same name, therefore the language is different" is the same as saying "I can no longer have varargs, therefore the language is different". – Cruncher Aug 26 '14 at 14:39
  • @ShadowsInRain: Sure they can -- most of them, anyway. What is `a + b`, if not just a different way of writing `a.plus(b)` or `add(a, b)`? You have a couple of essential ones that can't go away, but those would more correctly be *delimiters* rather than pure operators. – cHao Aug 26 '14 at 14:39
  • This answer is OK only for language supporting type inference, like Dart or Go. But has nothing to do with stuff like the paradigm. – Luis Masuelli Aug 26 '14 at 15:23
  • @cHao: `if` and `while` could be considered semantic sugar in a language which included `for` and `break`, or vice versa, but *some* sort of conditional construct is needed, and some sort of looping concept is needed. One could argue about which structure should be considered "fundamental" and which ones "sugar", but I would not regard as "sugar" a construct which cannot be emulated without adding flag variables, unless the compiler's own implementation would require similar variables. – supercat Aug 26 '14 at 20:05
  • @supercat: Check out Smalltalk for evidence that neither need be built into the language proper -- both are methods, and the decision is handled entirely via polymorphism. (`true` and `false` are singletons of different subclasses of `Boolean`.) (`true ifTrue: [do stuff].` runs the `[do stuff]` block, and `false ifTrue: [do stuff].` ignores it. Loops are done by telling a block that evaluates a condition, "run this other block while your own return value is (true|false)". No new flag variables required. You could easily build your own control structures if you wanted. – cHao Aug 26 '14 at 20:54
  • @cHao: Perhaps instead of saying "similar values", I should have had "comparable overhead". I would be very surprised if Smalltalk could use virtual dispatch to achieve speeds that are within an order of magnitude of what Java can achieve, and constructs which make it possible to write faster code than would otherwise be possible are decidedly *not* syntax sugar. – supercat Aug 26 '14 at 21:02
  • @supercat: Your definition of what isn't syntax sugar seems to be expanding. But it's irrelevant. If a compiler were to convert a Smalltalk image to native code, it could easily translate the polymorphism into a conditional branch if that makes things faster. I'm fairly certain Dolphin does just that. – cHao Aug 26 '14 at 21:08
  • @cHao: I've never looked at non-toy-level Smalltalk compilers, so you may be right. On the other hand, a Smalltalk compiler supports constructs which Java cannot support efficiently, so the fact that Java's conditional constructs may not be necessary in a language which efficiently supports constructs that Java does not hardly means those constructs would be "syntax sugar" in a language which didn't support those other constructs. – supercat Aug 26 '14 at 21:13
  • @supercat: Right. That was pretty much my point. The question (although it includes Java code) is largely language-agnostic, so we can't say any particular feature is or isn't "sugar". But in the end, it's all an abstraction over machine language anyway. – cHao Aug 26 '14 at 21:34
  • Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackexchange.com/rooms/16730/discussion-on-answer-by-kdgregory-is-method-overloading-anything-more-than-synta). –  Aug 26 '14 at 23:28
13

The term syntactic sugar typically refers to cases where the feature is defined by a substitution. The language doesn't define what a feature does, instead it defines that it is exactly equivalent to something else. So for example, for-each loops

for(Object alpha: alphas) {
}

Becomes:

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

Or take a function with variable arguments:

void foo(int... args);

foo(3, 4, 5);

Which becomes:

void Foo(int[] args);

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

So there is a trivial substitution of syntax to implement the feature in terms of other features.

Let's look at method overloading.

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

foo(4.5);

This can be rewritten as:

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

foo_double(4.5);

But it is not equivalent to that. Within the model of Java, this is something different. foo(int a) doesn't implement a foo_int function to be created. Java doesn't implement method overloading by giving ambiguous functions funny names. To count as syntactic sugar, java would have to be pretending that you really wrote foo_int and foo_double functions but it doesn't.

Winston Ewert
  • 24,732
  • 12
  • 72
  • 103
  • 2
    I don't think anyone ever said the transformation for syntax sugar has to be trivial. Even if it did, I find the claim that `But, the transformation isn't trivial. At the least, you have to determine the types of the parameters.` very sketchy because the types don't need to be *determined*; they're known at compile time. – Doval Aug 25 '14 at 18:20
  • @Doval, yeah, I didn't express that very well. Tried again. – Winston Ewert Aug 25 '14 at 18:44
  • 3
    "To count as syntactic sugar, java would have to be pretending that you really wrote foo_int and foo_double functions but it doesn't." - as long as we talk about overloading methods and not polymorphisms what would be a difference between `foo(int)`/`foo(double)` and `foo_int`/`foo_double`? I don't really know Java very well but I would imagine that such renaming really happens in JVM (well - probably using `foo(args)` rather then `foo_args` - it does at least in C++ with symbol mangling (ok - symbol mangling is technically an implementation detail and not part of language). – Maciej Piechotka Aug 25 '14 at 18:58
  • 2
    @Doval: "I don't think anyone ever said the transformation for syntax sugar has to be trivial." – True, but it has to be *local*. The only useful definition of syntactic sugar I know of is from Matthias Felleisen's famous paper on language expressivity, and basically it says that if you can re-write a program written in language **L+y** (i.e. some language **L** with some feature **y**) in language **L** (i.e. a subset of that language without feature **y**) without changing the global structure of the program (i.e. only making local changes), then **y** is syntactic sugar in **L+y** and does – Jörg W Mittag Aug 25 '14 at 19:02
  • 2
    … not increase **L**'s expressivity. However, if you *cannot* do that, i.e. if you have to make changes to the global structure of your program, then it is *not* syntactic sugar and *does* in fact make **L+y** more expressive than **L**. For example, Java with enhanced `for` loop is not more expressive than Java without it. (It's nicer, more concise, more readable, and all around better, I would argue, but *not* more expressive.) I am not sure about the overloading case, though. I'll probably have to re-read the paper to be sure. My gut says it *is* syntactic sugar, but I'm not sure. – Jörg W Mittag Aug 25 '14 at 19:10
  • 1
    @JörgWMittag This looks like a great paper, thanks for pointing me in that direction. I would imagine that if renaming a variable counts as a local change, so does renaming a function, and thus overloading qualifies. – Doval Aug 25 '14 at 19:14
  • 2
    @MaciejPiechotka, if it were part of the language definition that functions were so renamed, and you could access the function under those names, I think it would be syntactic sugar. But because its hidden as an implementation detail, I think that disqualifies it from being syntactic sugar. – Winston Ewert Aug 25 '14 at 19:32
  • @Doval, the question is at the call site. How does the compiler know which overload to call. Is it only doing that with local information? – Winston Ewert Aug 25 '14 at 19:46
  • @WinstonEwert What's your definition of "local information"? The function gets chosen based on the static type of the arguments, which is known at compile time. – Doval Aug 25 '14 at 19:54
  • When I think of a local transformation, I think of it as only using information at the call site. Thus information about which overloaded versions of a function exists is non-local. Basically, I think of something that just rewrites the AST without having to look at symbol tables, etc. – Winston Ewert Aug 25 '14 at 20:01
  • Winston, please look at the edit to my question. It clarifies why I think method overloading is syntatic sugar. Also, BTW if I'm not mistaken the Java compiler *does* compile `foo(int)` to `foo_i` and `foo(double)` too `foo_d`. It treats overloaded methods as completely different methods. – Aviv Cohn Aug 25 '14 at 21:03
  • @Prog, given http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.6, it looks to me like java does not compile `foo(int)` to `foo_i`, they are compiled to different methods but the methods are not distinguished by name. – Winston Ewert Aug 25 '14 at 21:33
  • The ONLY reason I would consider overloading in Java not sugar would be because of its reflection capabilities. In C++ we have templates, and thus generic semantics would get lost. If (and that's a big if) C supported overloading (with consistent name mangling semantics), that would be sugar. – Thomas Eding Aug 26 '14 at 03:31
  • @ThomasEding, What's your definition of "sugar"? – Winston Ewert Aug 26 '14 at 04:03
  • A language reduction from L to L' such that: (1) L contains L'. (2) For all language constructs in L, L' can express *identical* semantics. (Identical is stronger than equivalent.) – Thomas Eding Aug 26 '14 at 04:10
  • @ThomasEding, are for loops syntactic sugar for while loops? – Winston Ewert Aug 26 '14 at 15:44
  • @WinstonEwert: Very much so. Even `if` is sugar over `while`. – Thomas Eding Aug 26 '14 at 15:46
  • @WinstonEwert: But for the availability of Reflection, would there be any way of telling whether they were distinguished by name or not? Even with Reflection, would there be any way other than by examining bytecode to distinguish whether a compiler attached type information to names whenever methods were defined or called, and Reflection simply stripped that information when the names were retrieved? – supercat Aug 26 '14 at 17:50
  • @ThomasEding, my definition is that syntax sugar is a case where construct X is converted to construct Y in the same language as part of the compilation/interpretation of a program (at least conceptually). In C, `a[x]` is interpreted as `*(a+x)`. However, `if` doesn't get converted into a `while` during compilation, even if it theoretically could. – Winston Ewert Aug 26 '14 at 18:16
  • @ThomasEding, ultimately it comes down to differing definitions of the phrase. There's no point in arguing over the definition of words, I'll simply note that I believe mine is closer to how the phrase is typically used. But that's also possibly just my experience. Undoubtedly, there are also many uses that accord with your definition. – Winston Ewert Aug 26 '14 at 18:18
  • @supercat, if that's your criteria for deciding whether something is sugar, everything is sugar. That's seems an uselessly broad category. – Winston Ewert Aug 26 '14 at 18:21
  • @WinstonEwert: I wasn't seeking to define "sugar", but merely to ask whether there are any meaningful semantic differences, outside Reflection, between having multiple overloads with the name name, versus overloads with different names. There is one which hasn't been mentioned, which is that the way outside constructor calls are implemented would not allow a type to have more than one constructor except by using overloading. – supercat Aug 26 '14 at 19:57
  • Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackexchange.com/rooms/16729/discussion-on-answer-by-winston-ewert-is-method-overloading-anything-more-than-s). –  Aug 26 '14 at 23:28
8

Given that name-mangling works, doesn't it have to be nothing more than syntactic sugar?

It allows the caller to imagine he is calling the same function, when he isn't. But he could know the real names of all his functions. Only if it were possible to achieve delayed polymorphism by passing an untyped variable into a typed function and have its type established so that the call could go to the right version according to name would this be a true language feature.

Unfortunately, I have never seen a language do this. When there is ambiguity, these compilers do not resolve it, they insist the writer resolve it for them.

  • The feature you're looking for there is called "Multiple Dispatch". Plenty of languages support it including Haskell, Scala, and (since 4.0) C#. – Iain Galloway Aug 26 '14 at 14:49
  • I would like to separate parameters on classes from straight method overloading. In the straight method overloading case, the programmer writes all the versions, the compiler just knows how to choose one. That is just syntactic sugar, and is solved by simple name-mangling, even for multiple dispatch. --- In the presence of parameters on classes, the compiler generates the code as necessary, and that changes this completely. – Jon Jay Obermark Aug 26 '14 at 19:37
  • 2
    I think you misunderstand. For example, in C#, if one of the parameters to a method is `dynamic` then *overload resolution occurs at runtime, not at compile-time*. That's what multiple dispatch is, and it cannot be replicated by renaming functions. – Iain Galloway Aug 26 '14 at 22:09
  • Quite cool. I can still test for variable type, however, so this is still just a built-in function overlayed on syntactic sugar. It is a language feature, but only barely. – Jon Jay Obermark Aug 26 '14 at 22:19
8

Depending on the language, it is syntactic sugar or not.

In C++ for instance, you can do things using overloading and templates which would not be possible without complications (write manually all instantiations of the template or add a lot of template parameters).

Note that dynamic dispatch is a form of overloading, dynamically resolved on some parameters (for some languages only a special one, this, but not all languages are so limited), and I would not call that form of overloading syntactic sugar.

AProgrammer
  • 10,404
  • 1
  • 30
  • 45
5

For contemporary languages, it's just syntactic sugar; in a completely language-agnostic sort of way, it's more than that.

Previously this answer stated simply that it's more than syntactic sugar, but if you'll see in the comments, Falco raised the point that there was one piece of the puzzle that contemporary languages appear to all be missing; they don't mix method overloading with dynamic determination of which function to call in the same step. This will be clarified later.

Here's why it should be more.

Consider a language that supports both method overloading and untyped variables. You could have the following method prototypes:

bool someFunction(int arg);

bool someFunction(string arg);

In some languages, you would be probably be resigned to knowing at compile time which one of these would be called by a given line of code. But in some languages, not all variables are typed (or they're all implicitly typed as Object or whatever), so imagine building a dictionary whose keys map to values of different types:

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.

Now then, what if you wanted to apply someFunction to one of those room numbers? You call this:

someFunction(roomNumber[someSortOfKey]);

Is someFunction(int) called, or is someFunction(string) called? Here you see one example where these are not totally orthogonal methods, especially in higher-level languages. The language has to figure out - during runtime - which one of these to call, so it still has to regard these as being at least somewhat the same method.

Why not simply use templates? Why not simply use an untyped argument?

Flexibility and finer-grained control. Sometimes using templates / untyped arguments are a better approach, but sometimes they're not.

You have to think about cases where, for instance, you might have two method signatures that each take an int and a string as arguments, but where the order is different in each signature. You may very well have a good reason to do this, as each signature's implementation may do largely the same thing, but with just a slightly different twist; the logging could be different, for example. Or even if they do the same exact thing, you may be able to automatically glean certain information from just the order in which the arguments were specified. Technically you could just use pseudo-switch statements to determine the type of each of the arguments passed in, but that gets messy.

So is this next example bad programming practice?

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;
    }
}

Yes, by and large. In this particular example, it could keep somebody from trying to apply this to certain primitive types and getting back unexpected behavior (which could be a good thing); but let's just assume I abbreviated the code above, and that you, in fact, have overloads for all the primitive types, as well as for Objects. Then this next bit of code really is more appropriate:

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

But what if you only needed to use this for ints and strings, and what if you want it to return true based on simpler or more complicated conditions accordingly? Then you have a good reason to use overloading:

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;
    }
}

But hey, why not just give those functions two different names? You still have the same amount of fine-grained control, don't you?

Because, as stated before, some hotels use numbers, some use letters, and some use a mixture of numbers and letters:

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

This still isn't precisely the same exact code I would use in real life, but it should illustrate the point I'm making just fine.

But... Here's why it isn't more than syntactic sugar in contemporary languages.

Falco raised the point in the comments that current languages basically don't mix method overloading and dynamic function selection within the same step. The way I previously understood certain languages to work was that you could overload appearsToBeFirstFloor in the example above, and then the language would determine at runtime which version of the function to be called, depending on the runtime value of the untyped variable. This confusion partially stemmed from working with ECMA-sorts of languages, like ActionScript 3.0, in which you can easily randomize which function gets called on a certain line of code at runtime.

As you may know, ActionScript 3 doesn't support method overloading. As for VB.NET, you can declare and set variables without assigning a type explicitly, but when you try to pass these variables as arguments to overloaded methods, it still doesn't want to read the runtime value to determine which method to call; it instead wants to find a method with arguments of type Object or no type or something else like that. So the int vs. string example above wouldn't work in that language either. C++ has similar issues, as when you use something like a void pointer or some other mechanism like that, it still requires you to manually disambiguate the type at compile time.

So as the first header says...

For contemporary languages, it's just syntactic sugar; in a completely language-agnostic sort of way, it's more than that. Making method overloading more useful and relevant, like in the example above, may actually be a good feature to add to an existing language (as has been widely implicitly requested for AS3), or it could also serve as one among many different fundamental pillars for the creation of a new procedural / object-oriented language.

Panzercrisis
  • 3,145
  • 4
  • 19
  • 34
  • 3
    Can you name any languages that really handle Function-Dispatch at runtime and not compile-time? *ALL* languages I know require compile-time certainty of which function is called... – Falco Aug 26 '14 at 08:36
  • @Falco ActionScript 3.0 handles it at runtime. You could, for instance, use a function that returns one of three strings at random, and then use its return value to call any one of three functions at random: `this[chooseFunctionNameAtRandom]();` If `chooseFunctionNameAtRandom()` returns either `"punch"`, `"kick"`, or `"dodge"`, then you can thusly implement a very simple random element in, for instance, an enemy's AI in a Flash game. – Panzercrisis Aug 26 '14 at 12:42
  • @Falco Also some more static languages still allow you to define function objects relatively easily. C++ is an example. If you combine inheritance with classes that have overloaded the `()` operator, then you could set the value of a base class pointer to an instance of one of two randomly chosen subclasses, which would emulate the functionality of a function being selected at runtime. In some languages, you could also use delegates, function pointers, function references (such as in languages where functions are objects), etc. – Panzercrisis Aug 26 '14 at 12:46
  • 1
    Yes - but they are both real semantic methods to get dynamic function dispatch, Java has these as well. But they are different from overloading, overloading is static and just syntactic sugar, while dynamic dispatch and inheritance are real language features, which offer new functionality! – Falco Aug 26 '14 at 12:49
  • @Falco I kind of see what you're saying; even though some determine the functions at runtime, it looks like they don't generally like to mix that with overloading in the same statement or whatever. ActionScript 3's notorious for not having method overloading, but I figured that maybe an untyped variable in VB.NET might do the trick; that didn't pan out though when it tried, as it kept trying to look for a function that took an object (or if they do literally exist, an untyped variable)... – Panzercrisis Aug 26 '14 at 14:39
  • 1
    ...I also tried void pointers in C++, as well as base class pointers, but the compiler wanted me to disambiguate it myself before passing it to a function. So now I'm wondering whether to delete this answer. It's starting to look like languages almost always walk right up to combining dynamic function choice with function overloading in the same instruction or statement, but then step away at the last second. It would be a nice language feature though; maybe somebody needs to make a language that has this. – Panzercrisis Aug 26 '14 at 14:42
  • 1
    Let the answer stay, maybe think about including some of your research from the comments in the answer? – Falco Aug 26 '14 at 15:29
2

It really depends by your defintion of "syntactic sugar". I'll try to address some of the definitions that come to my mind:

  1. A feature is syntactic sugar when a program that uses it can always be translated in an other that doesn't use the feature.

    Here we are assuming that there exist a primitive set of features that cannot be translated: in other words no loops of the kind "you can replace feature X using feature Y" and "you can replace feature Y with feature X". If one of the two is true than either the other feature can be expressed in terms of features that aren't the first one or it is a primitive feature.

  2. Same as definition 1 but with the extra requirement that the translated program is as type-safe as the first, i.e. by desugaring you don't loose any kind of information.

  3. The definition of the OP: a feature is syntactic sugar if its translation doesn't change the structure of the program but only requires "local changes".

Let's take Haskell as example for overloading. Haskell provides user-defined overloading via type classes. For example the + and * operations are defined in the Num type class and any type that has a (complete) instance of such class can be used with +. For example:

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

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

One well-known thing about Haskell's type classes is that you can get rid of them. I.e. you can translate any program that uses type classes in an equivalent program that doesn't use them.

The translation is quite simple:

  • Given a class definition:

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

    You can translate it into an algebraic data type:

    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
    }
    

    Here X_P_i and X_op_i are selectors. I.e. given a value of type X a applying X_P_1 to the value will return the value stored in that field, so they are functions with type X a -> P_i a (or X a -> t_i).

    For a very rough anology you could think of the values for type X a as structs and then if x is of type X a the expressions:

    X_P_1 x
    X_op_1 x
    

    could be seen as:

    x.X_P_1
    x.X_op_1
    

    (It's easy to use only positional fields instead of named fields, but named fields are easier to handle in the examples and avoid some boiler-plate code).

  • Given an instance declaration:

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

    You can translate it into a function that given the dictionaries for the C_1 a_1, ..., C_n a_n classes returns a dictionary value (i.e. a value of type X a) for the type T a_1 ... a_n.

    In other words the above instance can be translated to a function like:

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

    (Note that n may be 0).

    And in fact we can define it as:

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

    where op_1 = ... to op_m = ... are the definitions found in the instance declaration and the get_P_i_T are the functions defined by the P_i instance of the T type (these must exist because P_is are superclasses of X).

  • Given a call to an overloaded function:

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

    We can explicitly pass the dictionaries relative to the class constraints and obtain an equivalent call:

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

    Note how the class constraints simply became a new argument. The + in the translated program is the selector as explained before. In other words the translated add function, given the dictionary for the type of its argument will first "unpack" the actual function to compute the result using (+) dictNum and then will apply this function to the arguments.

This is just a very quick sketch about the whole thing. If you are interested you should read the articles of Simon Peyton Jones et al.

I believe a similar approach could be used for overloading in other languages too.

However this shows that, if your definition of syntactic sugar is (1), then overloading is syntactic sugar. Because you can get rid of it.

However the translated program looses some information about the original program. For example it doesn't enforce that the instances for the parent classes exist. (Even though the operations to extract the parent's dictionaries must still be of that type, you can pass in undefined or other polymorphic values so you'd be able to build a value for X y without building the values for P_i y, so the translation doesn't loose all the type safety). Hence it's not syntacti sugar according to (2)

As for (3). I don't know whether the answer should be a yes or a no.

I'd say no because, for example, an instance declaration becomes a function definition. Functions that are overloaded get a new parameter (which means it changes both the definition and all the calls).

I'd say yes because the two program still map one-to-one, so the "structure" isn't actually changed that much.


This said, I'd say that the pragmatic advantages introduced by overloading are so big that using a "derogatory" term such as "syntactic sugar" doesn't seem correct.

You can translated all Haskell syntax to a very simple Core language (which is actually done when compiling), so most of Haskell syntax could be seen as "syntactic sugar" for something that is just lambda-calculus plus a bit new constructs. However we can agree that the Haskell programs are much easier to handle and are very concise, whereas the translated programs are quite harder to read or think about.

Bakuriu
  • 291
  • 2
  • 6
2

If the dispatch is resolved at compile time, depending only on the static type of the argument expression, then you can certainly argue that it's "syntactic sugar" replacing two different methods with different names, provided that the programmer "knows" the static type and could just use the right method name in place of the overloaded name. It is also a form of static polymorphism, but in that limited form it's usually not very powerful.

Of course it would be a nuisance to have to change the names of the methods you call whenever you change the type of a variable, but for example in the C language it's considered a manageable nuisance, so C doesn't have function overloading (although it does now have generic macros).

In C++ templates, and in any language that does non-trivial static type deduction, you can't really argue that this is "syntatic sugar" unless you also argue that static type deduction is "syntactic sugar". It would be a nuisance not to have templates, and in the context of C++ it would be an "unmanageable nuisance", since they're so idiomatic to the language and its standard libraries. So in C++ it's rather more than a nice helper, it's important to the style of the language, and so I think you have to call it more than "syntactic sugar".

In Java you might consider it more than just a convenience considering for example how many overloads there are of PrintStream.print and PrintStream.println. But then, there are as many DataInputStream.readX methods since Java doesn't overload on return type, so in some sense it is just for convenience. Those are all for primitive types.

I don't remember what happens in Java if I have classes A and B extending O, I overload methods foo(O), foo(A) and foo(B), and then in a generic with <T extends O> I call foo(t) where t is an instance of T. In the case where T is A do I get dispatch based on the overload or is it as if I called foo(O)?

If the former, then Java method overloads are better than sugar in the same way that C++ overloads are. Using your definition, I suppose in Java I could locally write a series of type checks (which would be fragile, because new overloads of foo would require additional checks). Aside from accepting that fragility I can't make a local change at the call site to get it right, instead I'd have to give up on writing generic code. I'd argue that preventing bloated code might be syntactic sugar, but preventing fragile code is more than that. For that reason, static polymorphism in general is more than just syntactic sugar. The situation in a particular language might be different, depending how far the language allows you to get by "not knowing" the static type.

Steve Jessop
  • 5,051
  • 20
  • 23
  • In Java, overloads are resolved at compile time. Given the use of type erasure, it would be impossible for them to be otherwise. Further, even without type erasure, if `T:Animal` is is type `SiameseCat` and existing overloads are `Cat Foo(Animal)`, `SiameseCat Foo(Cat)`, and `Animal Foo(SiameseCat)`, which overload should be selected if `T` is `SiameseCat`? – supercat Aug 26 '14 at 17:44
  • @supercat: makes sense. So I could have figured out the answer without remembering (or, of course, run it). Therefore, Java overloads are *not* better than sugar in the same way C++ overloads are relating to generic code. It remains possible there's some other way in which they're better than just a local transformation. I wonder should I change my example to C++, or leave it as some-imagined-Java-that-isn't-real-Java. – Steve Jessop Aug 26 '14 at 17:54
  • Overloads can be helpful in cases where methods have optional arguments, but they can also be dangerous. Suppose the line `long foo=Math.round(bar*1.0001)*5` gets changed to `long foo=Math.round(bar)*5`. How would that affect the semantics if `bar` equals, e.g., 123456789L? – supercat Aug 26 '14 at 18:00
  • @supercat I'd argue the real danger there is the implicit conversion from `long` to `double`. – Doval Aug 26 '14 at 19:56
  • @Doval: To `double`? – supercat Aug 26 '14 at 19:58
  • @supercat Sorry, I was referring to `bar*1.0001` where `bar == 123456789L`. I presume the intention is to call `Math.round(double)`, but when you remove a multiplication you end up rounding a `long`. – Doval Aug 26 '14 at 20:02
  • @Doval: And what happens when you round a `long`? – supercat Aug 26 '14 at 20:06
  • @supercat The compiler picks `Math.round(float)` and you end up with `12345790` from what I can tell. – Doval Aug 26 '14 at 20:15
  • @Doval: I meant to use a value large enough to overflow in the multiplication to illustrate the danger of using overloads to control return types (when the argument was type `double`, the return was type `long`, but with an argument of type `long` then return is type `int`). – supercat Aug 26 '14 at 20:31
1

It looks like "syntactic sugar" sounds derogatory, like useless or frivolous. That is why the question triggers many negative answers.

But you are right, method overloading doesn't add any feature to the language except for the possibility to use the same name for different methods. You can make the parameter type explicit, the program will still work the same.

The same applies to package names. String is just syntactic sugar for java.lang.String.

In fact, a method like

void fun(int i, String c);

in class MyClass should be called something like "my_package_MyClass_fun_int_java_lang_String". This would identify the method uniquely. (The JVM does something like that internally). But you don't want to write that. That is why the compiler will let you write fun(1,"one") and identify which method it is.

There is however one thing you can do with overloading: If you overload a method with the same number of arguments, the compiler will figure out automatically which version suits best the argument given by matching arguments, not only with equal types, but also where the given argument is a subclass of the declared argument.

If you have two overloaded procedures

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

you don't need to know that there is a specific version of the procedure for Dates. addParameter("hello", "world) will call the first version, addParameter("now", new Date()) will call the second one.

Of course, you should avoid overloading a method with another method that does a completely different thing.

Florian F
  • 1,127
  • 1
  • 6
  • 13
1

Interestingly, the answer to this question will depend on the language.

Specifically, there is an interaction between overloading and generic programming (*), and depending on how generic programming is implemented it might be just syntactic sugar (Rust) or absolutely necessary (C++).

That is, when generic programming is implemented with explicit interfaces (in Rust or Haskell, those would be type classes), then overloading is just syntactic sugar; or actually might not even be part of the language.

On the other hand, when generic programming is implemented with duck-typing (be it dynamic or static) then the name of the method is an essential contract, and therefore overloading is mandatory for the system to work.

(*) Used in the sense of writing a method once, to operate over various types in a uniform fashion.

Matthieu M.
  • 14,567
  • 4
  • 44
  • 65
0

In some languages it is no doubt merely syntactic sugar. However, what it is a sugar of depends on your point of view. I'll leave this discussion for later in this answer.

For now I'd just like to note that in some languages it is certainly not syntactic sugar. At least not without requiring you to use a completely different logic/algorithm to implement the same thing. It's like claiming recursion is syntactic sugar (which it is since you can write all recursive algorithm with a loop and a stack).

One example of a very hard to replace use comes from a language that ironically doesn't call this feature "function overloading". Instead it's called "pattern matching" (which can be viewed as a superset of overloading because we can overload not just types but values).

Here's the classic naive implementation of the Fibonacci function in Haskell:

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

Arguably the three functions can be replaced with an if/else as it is commonly done in any other language. But that fundamentally makes the utterly simple definition:

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

much messier and does not directly express the mathematical notion of the Fibonacci sequence.

So sometimes it can be syntax sugar if the only use is to allow you to call a function with different arguments. But sometimes it is much more fundamental than that.


Now for the discssion of what operator overloading may be a sugar for. You've identified one use-case - it can be used to implement similar functions that take different arguments. So:

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

can alternatively be implemented as:

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

or even:

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

But operator overloading may also be a sugar for implementing optional arguments (some languages have operator overloading but not optional arguments):

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

may be used to implement:

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

In such a language (google "Ferite language") removing operator overloading drastically removes one feature - optional arguments. Granted in languages with both features (c++) removing one or the other will have no net effect since either can be used to implement optional arguments.

slebetman
  • 1,394
  • 9
  • 9
  • Haskell is a good example of why operator overloading is not syntactic sugar, but I think a better example would be deconstructing an algebraic data type with pattern matching (something that is as far as I know impossible without pattern matching). – 11684 Aug 26 '14 at 08:08
  • @11684: Can you point to an example? I honestly don't know Haskell at all but found its pattern matching sublimely elegant when I saw that fib example (on computerphile on youtube). – slebetman Aug 26 '14 at 09:09
  • Given a datatype like `data PaymentInfo = CashOnDelivery | Adress String | UserInvoice CustomerInfo` you can pattern match on the type constructors. – 11684 Aug 26 '14 at 14:59
  • Like this: `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`. I hope this comment is somewhat understandable. – 11684 Aug 26 '14 at 15:05
0

I think it is simple syntactic sugar in most languages (at least all I know...) since they all require an unambigous Function-call at compile time. And the compiler simply replaces the function call with an explicit pointer to the right implementation signature.

Example in Java:

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

So in the end it could be completely replaced by a simple compiler-macro with search and replace, replacing overloaded Function mangle with mangle_String and mangle_int - since the argument-list is part of the eventual function-identifier this is practically what happens -> and therefore it is only syntactic sugar.

Now if there is a language, where the function is really determined at runtime, like with overridden Methods in objects, this would be different. But I don't think there is such a language, since method.overloading is prone to ambiguity, which the compiler cannot resolve and which has to be handled by the programmer with an explicit cast. This cannot be done at runtime.

Falco
  • 1,293
  • 8
  • 14
0

In Java type information is compiled in and which of the overloads is called is decides at compile time.

The following is a snippet from sun.misc.Unsafe (the utility for Atomics) as viewed in Eclipse's class file editor.

  // 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]

as you can see type information of the method being called (line 4) is included in the call.

This means that you could create a java compiler that takes type information. For example using such a notation the above's source would then be:

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

and the cast to long would be optional.

In other statically-typed compiled languages you will see a similar setup where the compiler will decide which overload will be called depending on types and include it in the binding/call.

The exception is C dynamic libraries where the type information is not included and trying to create an overloaded function will cause the linker to complain.

ratchet freak
  • 25,706
  • 2
  • 62
  • 97