I Am Hate Method Overloading (And So Can You!)

My hatred of method overloading has become a running joke at Guidewire. My hatred is genuine, icy hot, and unquenchable. Let me explain why.

First Principals
First of all, just think about naming in the abstract. Things should have good names. A good name is unique and easy to understand. If you have method overloading, the name of a method is no longer unique. Instead, the real name of the method is the sane, human chosen name, plus the fully qualified name of each argument’s type. Doesn’t that just seem sort of insane? If you are writing a tool that needs to refer to methods, or if you are just trying to look up a method reflectively, you have to know the name, plus all the argument types. And you have to know this even if the method isn’t overloaded: you pay the price for this feature even when it isn’t used.

Maybe that strikes you as a bit philosophical. People use method overloading in java, so there must be some uses for it. I’ll grant that, but there are better tools to address those problems.

In the code I work with day to day, I see method overloading primarily used in two situations:

Telescoping Methods
You may have a function that takes some number of arguments. The last few arguments may not be all that important, and most users would be annoyed in having to figure out what to pass into them. So you create a few more methods with the same name and fewer arguments, which call through to the “master” method. I’ve seen cases where we have five different versions of a method with varying numbers of arguments.

So how do I propose people deal with this situation without overloading? It turns out to be a solved problem: default arguments. We are (probably) going to support these in the Diamond release of GScript:

  function emailSomeone( address:String, subject:String, body:String,
                         cc:String=null, logToServer:boolean=false, 
                         html:boolean = false ) {
    // a very well done implementation
  }

A much cleaner solution. One method, with obvious syntax and, if your IDE is any good, it will let you know what the default values of the option arguments are (unlike method overloading.)

True Overloading
Sometimes you truly want a method to take two different types. A good example of this is the XMLNode.parse() method, which can take a String or a File or an InputStream.

I actually would probably argue with you on this one. I don’t think three separate parse methods named parseString(), parseFile() and parseInputStream() would be a bad thing. Code completion is going to make it obvious which one to pick and, really, picking a unique name isn’t going to kill you.

But fine, you insist that I’m a terrible API designer and you *must* have one method. OK, then use a union type (also probably available in the Diamond release of GScript):

  function parse( src:(String|File|IOStream) ) : XMLNode {
    if( src typeis String ) {
      // parse the string
    }
    ...
  }

A union type lets you say “this argument is this type or that type.” It’s then up to you to distinguish between them at runtime.

You will probably object that this syntax is moderately annoying, but I’d counter that it will end up being fewer lines of code than if you used method overloading and that, if you really want a single function to handle three different types, you should deal with the consequences. If it bothers you too much, just pick unique names for the methods!

So?
Let’s say you accept my alternatives to the above uses of method overloading. You might still wonder why I hate it. After all, it’s just a feature and a pretty common one at that. Why throw it out?

To understand why I’d like to throw it out, you have to understand a bit about how the GScript parser works. As you probably know, GScript makes heavy use of type inference to help developers avoid the boilerplate you find in most statically typed languages.

For example, you might have the following code:

  var lstOfNums = {1, 2, 3}
  var total = 0
  lstOfNums.each( \ i -> { total = total + i  } )

In the code above, we are passing a block into the each() method on List, and using it to sum up all the numbers in the list. ‘i‘ is the parameter to the block, and we infer it’s type to ‘int’ based on the type of the list.

This sort of inference is very useful, and it takes advantage of context sensitive parsing: we can parse the block expression because we know the type of argument that each() expects.

Now, it turns out that method overloading makes this context sensitive parsing difficult because it means that when you are parsing an expression there is no guarantee that there is a single context type. You have to accept that there may be multiple types in context when parsing any expression.

Let me explain that a bit more. Say you have two methods:

  function foo( i : int ) {
  }
  
  function foo( i : String ) {
  }

and you are attempting to parse this expression:

  foo( someVar )

What type can we infer that the context type is when we parse the expression someVar? Well, there isn’t any single context type. It might be an int or it might be a String. That isn’t a big deal here, but it becomes a big deal if the methods took blocks, or enums or any other place where GScript does context type sensitive parsing. You end up having lists of context types rather than a single context type in all of your expression parsing code. Ugly.

Furthermore, when you have method overloading, you have to score method invocations. If there is more than one version of a method, and you are parsing a method invocation, you don’t know which version of the method you are calling until after you’ve parsed all the arguments. So you’ve got to run through all the argument types and see which one is the “best” match. This ends up being some really complicated code.

Complexity Kills

Bitch, bitch, moan, moan. Just make it work, you say. If the java guys can do it, why can’t you? Well, we have made it work (for the most part.) But there’s a real price we pay for it.

I’m a Berkeley, worse-is-better sort of guy. I think that simplicity of design is the most important thing. I can’t tell you how much more complicated method overloading makes the implementation of the GScript parser. Parsing expressions, parsing arguments, assignability testing, etc. It bleeds throughout the entire parser, its little tentacles of complexity touching places you would never expect. If you come across a particularly nasty part of the parser, it’s a good bet that it’s there either because of method overloading or, at least, is made more complicated by it.

Oh, man up! you say. That’s the parser’s and tool developer’s problem, not yours.

Nope. It’s your problem too. Like Josh Bloch says, projects have a complexity budget. When we blow a big chunk of that budget on an idiotic feature like method overloading, that means we can’t spend it on other, better stuff.

Unfortunately, because GScript is Java compatible, we simply can’t remove support for method overloading. If we could though, GScript would have other, better features and, more importantly, fewer bugs.

That is why I am hate method overloading. And so can you.


11 Comments on “I Am Hate Method Overloading (And So Can You!)”

  1. x says:

    Whenever I see someone complain against method overloading, it turns out that they are a compiler author. It just never misses.

    Which makes me think that the argument is mostly that they don’t want (or can’t) implement it and they are trying to post-rationalize this choice with some weak excuses.

    Sorry, buddy, but from a user’s perspective, overloading (true overloading, as you call it) makes code more readable, and default arguments don’t cover all the cases where overloading is useful.

    Get to work :-)

  2. Raoul Duke says:

    is GScript released to the world yet? :-) :-)

    • Carson Gross says:

      Not yet. I’d bet you’ll have something to play with (and make fun of) by the end of the year.

      Cheers,
      Carson

  3. Doug says:

    I think x’s complaint really asks for us to understand just what we’re giving up to have method overloading. We imagine that it is essentially free: someone just has to do hard work. But it isn’t free: someone has to do hard work, and so something else doesn’t get done. The question is, could we get a list of features that we DON’T get because we get method overloading instead. Then we could really weigh their relative values and ‘decide’ which we’d rather have.

  4. Carson Gross says:

    x,

    I hated method overloading before I started working on the GScript compiler (you can ask the poor saps who have to work with me at Guidewire) but working on it definitely cemented that hatred.

    You win, because java supports it, and because we’ve already implemented it. But it is an awful feature that trashes the internals of the compiler for what even the most ardent supporters of method overloading must admit is a very infrequently used feature. The power to weight ratio is all wrong.

    Worse is better,
    Carson

  5. dw says:

    Once we have unions in GScript, will that make things
    better? The type of someVar in your example could then
    be (int|String)

  6. John "Z-Bo" Zabroski says:

    saw this on Artima….

    agree completely.

    method overloading simply indicates a problem domain abstraction problem.

  7. ianam says:

    Sorry, but “it’s expensive to implement” is simply not a rational reason for a user to hate a feature. If a language designer/implementer tells me they couldn’t give me feature A because feature B was so expensive, I may be disappointed and I may go find another language that provides A — possibly because the implementers have the resources to provide both A and B — but I won’t hate B simply for being costly; I will judge it on its usability merits.

    • Carson Gross says:

      That is short sighted of you: you are looking only at the benefit side of the ledger and ignoring the cost side of it.

      As a technical consumer of software who also produces software, you should understand that every feature has a complexity budget and that features that interact combinatorially with other features have an exponential budget: they are a complexity boondoggle. You should understand that you will be paying a very real cost for this boondoggle: more bugs, more corner cases and slower future development of the software. The costs are unseen without a bit of reflection on the nature of software, but they are very real, and perpetually lurking, perpetually threatening to tip the codebase over into the watery grave of ‘Don’t Touch Anything!’ that consumes so much software.

      In this case, given that there are other, more generally useful, language features that provide the same functionality (names/default args and union types) even the benefit side of the ledger is weak. If it were a line item on the California State budget, both parties would agree on it as the first thing to go.

      As I see it, the only rational response for a user is to drop everything he or she is doing and immediately begin work on a time machine, hot tub or otherwise, in order to return to 1991 to prevent this feature from being introduced to Java.

      Cheers,
      Carson

  8. Eric Giese says:

    In fact it IS a user’s problem and just one of the compiler. Method-Overloading kills contravariant argument types in oo languages.

    Lets say you have a method
    def hit(s: String)

    and a subclass, which is able to hit anything
    def hit(a: Object)

    In Java, this won’t go, because it is considered overloading. However, in principle such an operation is completely typesafe. Also, Method Overloading is an incomplete feature usually, because you cannot overload return types.

    And finally: An unneeded one. As pointed out, unions, or generally any sort of pattern matching with a single method is far more extensible than any kind of overloading.

    Its a different affair with dynamic multiple dispatch (clojure), though.
    Example

    class Boxer{
    def hit(b: Boxer)
    }

    class Fighter extends Boxer{
    def hit(f: Fighter)
    }

  9. Eric Giese says:

    “Example

    class Boxer{
    def hit(b: Boxer)
    }

    class Fighter extends Boxer{
    def hit(f: Fighter)
    }”

    These lines ended up wrongly in this post. damn.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 39 other followers