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.

18
  • 50
    In the end, any programming language feature is just syntactic sugar for raw assembler.
    – Philipp
    Aug 25, 2014 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, 2014 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, 2014 at 18:20
  • 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, 2014 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". Aug 25, 2014 at 19:13

12 Answers 12

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.

30
  • 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, 2014 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, 2014 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, 2014 at 17:59
  • 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, 2014 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, 2014 at 19:25
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.

23
  • 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, 2014 at 18:20
  • 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). Aug 25, 2014 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 Aug 25, 2014 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. Aug 25, 2014 at 19:10
  • 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. Aug 25, 2014 at 19:32
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.

4
  • The feature you're looking for there is called "Multiple Dispatch". Plenty of languages support it including Haskell, Scala, and (since 4.0) C#. Aug 26, 2014 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. Aug 26, 2014 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. Aug 26, 2014 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. Aug 26, 2014 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.

1
  • I'm not sure how the other answers are doing so much better when essentially incorrect.
    – Telastyn
    Aug 26, 2014 at 13:56
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.

7
  • 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, 2014 at 8: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. Aug 26, 2014 at 12:42
  • 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, 2014 at 12:49
  • 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. Aug 26, 2014 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, 2014 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.

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.

9
  • 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, 2014 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. Aug 26, 2014 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, 2014 at 18:00
  • @supercat I'd argue the real danger there is the implicit conversion from long to double.
    – Doval
    Aug 26, 2014 at 19:56
  • @Doval: To double?
    – supercat
    Aug 26, 2014 at 19:58
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.

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.

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.

4
  • 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, 2014 at 8: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, 2014 at 9:09
  • Given a datatype like data PaymentInfo = CashOnDelivery | Adress String | UserInvoice CustomerInfo you can pattern match on the type constructors.
    – 11684
    Aug 26, 2014 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, 2014 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.

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.