Hacker News new | past | comments | ask | show | jobs | submit login
MultiMethods for Python (alexgaynor.net)
24 points by twampss on June 26, 2010 | hide | past | favorite | 10 comments



The difference between this and true overloaded methods (which, correct me if I'm wrong, is the what he meant by "MultiMethod") is that an overloaded method is created at compile-time and does not incur the added cost of determining the type of the object at runtime.

That being said, I didn't know that Python doesn't have function signatures. Would someone care to comment on how a function is identified?


Multi-methods are not overloaded functions (in the C++ sense). You are correct that overloaded function calls are resolved at compile-time while multi-method calls are resolved at runtime, but if you want to make a C++ analogy, you'd better compare them to virtual methods. Multi-methods work almost like virtual methods, with two exceptions:

1) multi-methods are declared outside the class definitions; it is important since it allows you to extend a class without directly modifying its definition or implementation.

2) virtual functions do dispatch by one argument (`this` in C++) while multi-methods could perform dispatch by an arbitrary number of arguments.

That said, I don't like this implementation that much since it does not respect class inheritance. Suppose you defined a MultiMethod with two registered implementations, for class A and class B, where B is inherited from A. Then the result of the multi-method call with an object of type B is undefined. A proper multi-method would call the most specific implementation in this case.


In C++, methods are only dispatched by the type of the arguments. In, for example, Clojure's multimethods you can dispatch on pretty much anything: type of some or all arguments, their values, function of either (or both)... etc.

Quick example:

    (defmulti foo #(* % 100))
    (defmethod foo 0 [_] "passed in zero")
    (defmethod foo 100 [_] "passed in one")
    (defmethod foo 200 [_] "passed in two")
    (defmethod foo :default [n] (str "passed in " n))
    ...
    >>> (foo 2)
    "passed in two"
    >>> (foo 5)
    "passed in 5"
(written off the top of my head, so excuse any mistakes..)

I'm only using a single argument here and I'm dispatching on a function of its value. I could just as easily dispatch on multiple arguments (or an arbitrary amount).


I wonder how Clojure deals with ambiguities when dispatching by arbitrary predicates. Also in this case, the overhead of of generic call will grow linearly with the number of methods, right?

In Python, you could limit generic dispatch to argument type only without losing generality since you could override the `isinstance` operator. For instance, you could define a `const()` type so that `isinstance(x, const(100))` is true if and only if `x == 100`. Then your example could be written in Python as:

  foo = generic()

  foo.when(const(0))
  def foo_0(x): print "passed in zero"
  foo.when(const(100))
  def foo_100(x): print "passed in 100"
  foo.when(const(200))
  def foo_200(x): print "passed in 200"
  foo.when(int)
  def foo_default(x): print "passed in", x
This is not as elegant as in a language with native support for generic functions, but it works. I wish mainstream languages started to adopt multi-methods; it's conceptually very simple and much more extension-friendly than traditional OOP. Sadly it doesn't seem to happen.


the overhead of of generic call will grow linearly with the number of methods

As far as I know, no. I could be wrong, but I think how it works is this: each method is added to a map for that multimethod. When dispatching, the dispatch function is applied to the arguments and the result is used as the key into the map of methods. I'm pretty sure that the compiler could optimise the lookup too, though I don't know if Clojure does or not.

Thats just what I would do if I wrote Clojure - I don't actually know how Clojure manages it in real life (and am too lazy to check).

Yeah. I've always wished that languages like C++ had two things: multimethods and ML-style pattern matching (well, three things - I guess you'd want ML-style parametric types to go with the pattern matching).


This is correct, C++ does dispatch based of the type of the arguments. That's how also ties in further into how linking and name mangling works.


The difference between method overloading and true multimethods (see CLOS, Dylan, Perl 6...) is that multimethods are resolved runtime (depending on actual argument types or even their values itself).


It is true that one of the advantages of overloaded methods is that the cost is payed at compiler time, but it does also limit its flexibility, since it can only dispatch based on the information available at compile time (this isn't enough to implement oop polymorphism for example, except if you know no derived class override the method).

Python makes this much more complicated, because it doesn't have types (actually all the variables have dynamic types, so the type of a variable depends on what value it has) - and it is interpretated, so there isn't a compile step where it could resolve something like this.

Oh and usually multimethods dispatch based on the actual type of _multiple_ arguments, not just one.


I think Erlang does MultiMethods properly, it will call a function based on it's arity...


You shouldn't test for arguments with isinstance(). Better test for specific functionality. It's like FreeBSD developers being angry that people use tests like 'somecmd --version' in autoconf scripts and thus software not compiling even though required functionality was available.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: