top | item 3323518

Five-minute Multimethods in Python

113 points| llambda | 14 years ago |artima.com | reply

48 comments

order
[+] tikhonj|14 years ago|reply
The definition of multimethods presented here sounds suspiciously like ad-hoc polymorphism (or just overloading functions). It seems like the real difficulty is not in the concept but in the fact that I've now seen under three different names: multiple-dispatch, ad-hoc polymorphism and overloading. All but the last name seem complex; since I initially encountered the concept in Java as overloading, I never thought much about it.

Polymorphism based on types like this is something that statically typed languages are naturally good at. Looking at his example code, the trick to having this sort of code in dynamically typed Python is to introduce type annotations via decorators. You're doing the work of static typing for a fraction of the benefits.

Additionally, a statically typed language can take this concept even further. Haskell, for example, supports polymorphic functions like this very well using type classes. It even allows functions polymorphic on their return types--you can have a function `read` which takes a String and returns whatever. This means you can actually even have polymorphic constants--maxBound, for example, is the maximum value of whatever numerical representation it happens to be.

Overall, I think features like this make more sense in a statically typed language. They fit in--you're already specifying types everywhere--and can do more. However, it is interesting to see how you could accomplish something like this in a dynamically typed language.

[+] swannodette|14 years ago|reply
I think you're getting distracted by the simplistic implementation presented here. multimethods are plenty powerful outside of static types - see CLOS, Clojure.

That said multimethods are a halfway step to predicate dispatch (this is mentioned in the article comments). With predicate dispatch you can get efficient dispatch on any interesting predicate - not just types - for example even?, odd?, structural matching, etc.

I think dynamic languages could benefit a lot from predicate dispatch.

[+] jules|14 years ago|reply
Multimethods and overloading are completely different beasts, despite the syntactic similarity. Overloading is resolved at compile time by looking at the compile time types. Multimethods are dispatched at run time, by looking at the run time values. This makes overloading much less powerful. Multimethods are in fact problematic in statically typed languages; they are difficult (but not impossible) to type check.
[+] bad_user|14 years ago|reply
Haskell is incompatible with OOP precisely because in Haskell everything is solved at compile-time. It's a different way of thinking that has both advantages and disadvantages, however you cannot move these concepts around easily (i.e. not many features in Haskell are compatible with Java or viceversa - for instance Microsoft tried implementing Haskell on top of .NET and simply gave up).

    ad-hoc polymorphism
Yes it is - however, the difference between multi-methods and the polymorphism you get in Java (and Python) is that Java does runtime single-dispatch on the implicit parameter only, whereas a multimethod can do runtime dispatch on all parameters.

Plus, because it is at runtime, you can also give it conditions that aren't necessarily related to the types involved (depending on implementation of course).

    Overall, I think features like this make more sense 
    in a statically typed language
You've got it backwards - a lot of the features in statically typed languages like Haskell or Java (e.g. overloading, since you mentioned it) are there to be a replacement for multi-methods. But the feature is so powerful that it hardly has any substitute (just like macros).
[+] andrewcooke|14 years ago|reply
pytyp will also do this, if anyone is interested - http://acooke.org/pytyp/pytyp.spec.dispatch.html

(in addition, it can be used to add type checking, and type-guided translation between json and python objects. it will also dispatch on "compound" types, like a list of integers).

[+] agentultra|14 years ago|reply
http://en.wikipedia.org/wiki/Multiple_dispatch#Common_Lisp

I tend to prefer the (defmethod) form in CL. You get generic functions which can retrieve the function object specialized for the parameters with (find-method). You also get the auxiliary method goodness...

But the great thing with Python that the article shows is that there's probably a way you can get those things in Python too if you needed them.

So does Python have multiple dispatch or can you just implement it?

[+] swannodette|14 years ago|reply
The challenge with expressive features like multimethods - does your language give you the tools not only to implement it, but to make it fast? This is yet another benefit of macros - the ability to add features like this and get good, perhaps even great, performance.
[+] antihero|14 years ago|reply
"a function that has multiple versions, distinguished by the type of the arguments"

Ooh, C# style overloading.

[+] Anchor|14 years ago|reply
Yes, but not quite. Consider the following example:

  static void Main()
  {
    Bar( 12, 15 );
  }

  static void Bar( object o1, object o2 )
  {
    Foo( o1, o2 ); // error
  }

  static void Foo( int x, double y )
  {
    Console.WriteLine( "int double" );
  }

  static void Foo( double x, int y )
  {
    Console.WriteLine( "double int" );
  }

  static void Foo( double x, double y )
  {
    Console.WriteLine( "double double" );
  }

  static void Foo( int x, int y )
  {
    Console.WriteLine( "int int" );
  }
This does not compile as the compiler does not know which of the four versions it should call. As jules said overloading is resolved at compile time by looking at the compile time types. Multimethods are dispatched at run time, by looking at the run time values. In this case that would be the "int int" version.
[+] unknown|14 years ago|reply

[deleted]

[+] judofyr|14 years ago|reply
The difference between multimethods and pattern matching is that pattern matching is usually "closed" (you can't add new clauses somewhere else), while multimethods allow you to extend the method with additional functionality.
[+] SammyRulez|14 years ago|reply
The power of a static typing in a dynamic typed language.. that's great.. and elegant too! GO BDFL !
[+] radarsat1|14 years ago|reply
Actually it's just checking the types at runtime, nothing much 'static' about it. This is pretty much the antithesis of duck typing, in fact. I could see it being useful, but I could also see it leading to confusion for people who expect Python's usual duck-typing behaviour when they pass their own types into a function.

Whoops you subclassed from a type for which this function has a multimethod defined? Gonna do something different.

On the other hand, in non-trivial programs there _are_ certainly occasions when you need to check the type, so using an elegant method like this isn't a bad compromise.

[+] tikhonj|14 years ago|reply
Except that you have to statically specify the types using decorators. So it's more like a subset of static typing in a dynamically typed language.
[+] mhb|14 years ago|reply
Not the main point but if the if-else pattern gets tedious why is there no switch in Python?
[+] icebraining|14 years ago|reply
Because a switch can usually be replaced by a dictionary?

    { 1: runOne, 2: runTwo, 3:runThree }[n]()
is equivalent to

    switch(n) { 
        case 1: runOne(); break; 
        case 2: runTwo(); break;
        case 3: runThree(); break;
    }
[+] tikhonj|14 years ago|reply
In this case, a switch statement wouldn't help because you're dealing with a bunch of boolean functions rather than a single value. If you were using something like typeof and switched on the type, a switch statement would make more sense. However, even that would be pretty tedious for code like this, I think.

In general though, I have no idea why Python doesn't have a switch or case statement of some sort.

[+] rplnt|14 years ago|reply
It's not necessary. If there is long if/else chain in your code, something is probably wrong. In case of python (and other OO, weakly typed languages).

But my opinion is that it wouldn't hurt to have it there.

[+] datashaman|14 years ago|reply
This obfuscates code, and I remove it wherever I see it. Sometimes people are too clever for their own good. Use an object. Override a method, FFS.
[+] barrkel|14 years ago|reply
Any trivial example demonstrating how something like this works will seem obfuscatory, because examples are usually trivially solved with other approaches. But see the Expression Problem[1] for the deep problem.

Whether it's an appropriate solution depends on whether introducing new operations or new data types is more common; and where the modularity boundaries are. For example, what if the base class in the hierarchy doesn't have a method for the task you want to perform, and you can't add one because it belongs to third party code, and nor can you implement it appropriately for every descendant?

[1] http://en.wikipedia.org/wiki/Expression_problem