draft-horses

Posted by & filed under Journals.

Fork me on GitHub

Here’s the tl;dr

never use method overloading because it sucks

Method overloading sucks in Scala because it actually detracts from your flexibility. An implicit conversion is a feature of Scala that lets the compiler look up how to convert objects between types at compile time.

Let’s say I define a method with the following signature:

def doSomething( action: Action )

Somewhere else in the code I write:

val idea: Idea = // some idea
doSomething( idea )

If Idea is a subtype of Action most languages will have no problem, including Scala. If Idea is not a subtype of Action this would be a compile-time error in languages like Java. The Scala compiler, however, doesn’t give up just yet. Instead of just throwing a type error, the Scala compiler searches for an implicit conversion between Idea and Action.

An implicit conversion is just a method marked implicit that has the right signature, in this case

implicit def anyNameWillDo( idea: Idea ): Action

The only caveat is that the compiler must be able to find the conversion along a pre-determined search path. The compiler will check, in order, the local scopes, followed by the companion objects of Action and Idea. Only if no implicit conversion can be found will the compiler issue a type error.

Overloading is Not the Answer

The problem with operator overloading, and why is sucks, is because overloaded methods interfere with implicit conversions. Let us overload the doSomething method to accept both an Action and Idea.

def doSomething( action: Action )
def doSomething( idea: Idea )

Now our implicit conversion, which we had full control over will no longer work. We’re stuck with whatever conversion the overloaded method uses.

Adding new overloaded methods requires modifying the target class, which is not always possible.

By using implicit conversions your code will look cleaner. The class containing the method doSomething need only implement the method that responds to an Action. All conversion code is located elsewhere in the code base.

Implicit conversions are determined at compile time. Typically default conversions are in the companion objects or imported from a package. If you don’t like the default conversion, bring another implicit into a nearer scope than the default.

Methods containing multiple arguments can mix and match conversions. Say we define:

def doThreeThings( first: Action, second: Action, third: Action )

Implicit conversions let us do any of the following:

doThreeThings( idea, idea, idea )
doThreeThings( idea, idea, action )
doThreeThings( idea, action, idea )
doThreeThings( idea, action, action )
doThreeThings( action, idea, idea )
doThreeThings( action, idea, action )
doThreeThings( action, action, idea )
doThreeThings( action, action, action )

To accomplish the same thing with method overloading, we would require eight separate methods.

Just Say No

As Tim points out below, since Scala supports default method arguments, we can avoid the following:

doSomeThings( action: Action )
doSomeThings( action: Action, action: Action )
doSomeThings( action: Action, action: Action, action: Action )

Imagine how many methods we would need to define if we wanted to also accept Idea types! We would need 14 separate methods.

  • Tim

    You for about default arguments, e.g. def foo(arg1: String = “Bob”, arg2: SomeType = someValue) removes another big chunk of the use case of overloading.

    • http://www.underflow.ca/ Jacob Groundwater

      Added this to the end, good point.

  • Anonymous

    I’d say you’re a bit confused.  At the very least, your example makes no sense.  If you have a doSomething(Action) and a doSomething(Idea) then this is arguably quite superior to an implicit conversion, since the implicit conversion is not tied to the context of “doSomething”; it will convert no matter what.  It doesn’t matter whether the conversion should be applicable or not, it will do it anyway.

    The fact that you’re mixing implicit conversions with overloading in an odd way, makes it… well, odd.  Nobody should be surprised by this.  Use implicit conversions for what they’re intended for, and use overloading for what’s intended for.  If you purposely make them clash with each other, then you’ve discovered what a lot of people already know: they’re not intended for the same purpose.

    • http://www.underflow.ca/ Jacob Groundwater

      If you really need your class to be in control of how a type is used, use a method with a different name. It is trivial to do, and doesn’t interfere with implicit conversions. This is much less ambiguous as to intention. I’m not saying you have to use implicits, but using method overloading will interfere with anyone who wishes to use them.

      There is no necessary use case for method overloading other than convenience. In Java this was great since you do not need to memorize a lot of method names. In Scala, there are much better ways.

  • http://twitter.com/nuttycom Kris Nuttycombe

    Target-typed implicit conversions (such as “implicit def a2i(a: Action): Idea) are a design error in Scala. The reason that these are a design error is that they can inadvertently be applied when inappropriate, and are obfuscatory. Implicit conversions are suitable for implementing the “pimp my library” pattern, but not for the purpose you suggest in this article; and, indeed, such conversions will be restricted behind flags after SIP-18 goes through.

    In short, don’t do this. If you want to convert from Idea to Action, do so explicitly; add .toAction to Idea (perhaps via a pimp-my-library implicit) and call as doSomething(idea.toAction)

    • http://www.underflow.ca/ Jacob Groundwater

      I can’t find anything to support your assertion that they are a design error.

      An implicit conversion of the kind I used is pretty much as explicit as it gets. In fact, it’s exactly what is suggested by “Pimp my library” (http://www.artima.com/weblogs/viewpost.jsp?thread=179766). The conversions used in my example are akin to (val x:Int = “2″).

      Most IDEs will tell you exactly what type your object is, and what type is expected. It should also show you the implicit conversion used.

      Your suggestion still requires implicit conversion, but what I consider the less obvious kind. Where .toAction is attached to some helper class with an implicit conversion. Unless you suggest adding a .toAction method to all Idea classes, which I would consider a design error.
      As for SIP-18, it looks pretty controversial so I’ll wait until it gets implemented before giving it much weight. Even when it gets implemented, it’s not a problem, you would just explicitly mention that you wish to add implicit conversions. This does not mean they are discouraged, any more than importing a collection is discouraged, rather they are just trying to organize the language features of a rapidly growing platform.

      I would concede that placing the implicit conversions within the companion object might confuse an newbie scala developer who wasn’t sure why their code was compiling what looks like an obvious type error. I am however arguing against method overloading, and am asserting that there are better, cleaner, more controllable ways.

    • Tim

      Another blog I follow says that SIP-18 is a terrible idea:  http://blog.tmorris.net/sip-18-is-just-another-bad-idea-serving-nobody/

      • the_unloginable

        SIP-18 is a terrible idea, but that doesn’t make the idea presented here any better

        • http://www.underflow.ca/ Jacob Groundwater

          I have thought long about peoples objections to this post, and I think it’s because of assumptions I made about using overloading.

          I would never overload a method unless I was doing a conversion on the argument types. i.e. if my method loadData( x ) accepted a URL, File Pointer, String, or Byte Array. In such a case, implicit conversion makes a lot of sense, since the loadData method is really acting on only one type of data, just from different sources.

          However it seems like people are using overloaded methods for just about anything. This is even worse behaviour IMHO. Nobody is forcing anyone to use implicits, but I see even less of a reason to use method overloading.

      • http://www.underflow.ca/ Jacob Groundwater

        SIP-18 is an interesting idea, but I don’t think they have thought it through enough. I think Martin, who seems to be the only reason it is being pushed forward, is envisioning language features like libraries. You import what you need, and leave the rest.

        A side effect of this is that people are championing the idea of divergent language features. i.e. you could not import the entire set of features because they have conflicting behaviour. Now as cool as that sounds from a hacking point of view, it would probably accomplish the exact opposite of what SIP-18 is trying to accomplish.

  • Pingback: This week in #Scala (25/05/2012) | Cake Solutions Team Blog