Five-minute Multimethods in Python
artima.com
artima.com
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.
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.
(Overload resolution is a pattern matching task, selecting a signature from a list of signatures, given a tuple of candidate value types[1]. It's not just used for invocation, but also when e.g. determining which overload of a function to assign to a function pointer.)
[1] The set of value types is usually larger than the set of types available for use in signatures. For example, most static languages don't have a name for the type of the 'null' or 'nil' value, so you usually can't refer to it in a signature (and nor would it be particularly useful to do so).
Multimethods, to a first approximation, is overload resolution at run time rather than compile time. They are not just syntactically similar; they are semantically similar too. Multimethods is the dynamic language analogue to overloading in static languages.
This implies that multimethods are not possible in statically typed languages, which is trivially disproven by stuff like the Nice programming language.
The difference between compile and run time is a red herring, since the actual distinction is between a statically _declared_ type and a dynamic type (which FWIW could be determined statically within AOT compilation).
[1] It shouldn't need to be said, but this is HN so it does need to be said, but I mean OO-style runtime polymorphism here (or, with a stretch, sum types), not parametric polymorphism.
I'm using them in a colloquial rather than rigorous sense, because that's the lowest friction way of communicating in a casual setting.
But at core, when I say 'static', I mean something determined before execution, by means of static scopes and compile-time types; when I say dynamic, I mean something determined at the point of execution, by means of dynamic scopes and run-time values. When I say 'static scopes', I mean scopes determined through lexical nesting and static types. When I say 'static type', I mean types determined without need for any execution of the target program. When I say 'dynamic scopes', I mean scopes determined through dictionary lookup at runtime (vtables are dictionaries too), possibly even including dynamic scopes (cf Lisp, Javascript eval, etc.).
Most languages have both dynamic and static aspects, but the mixes vary greatly. Languages designed to be compiled typically use far more statically resolved features, because if you can afford the time to put into analysis, and you have all the source immediately available, you can find a lot of common bugs and generate faster code. Languages designed to be interpreted typically use far more dynamically resolved features, because that gives less latency to execution, more expressiveness, more flexibility with modularity and program composition, and is also easier to implement.
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).(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).
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?
https://gist.github.com/1320421
Not to actually use, of course.
Ooh, C# style overloading.
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.That's cool and all, but why wouldn't I just define an interface with method foo(), then override foo on my classes? This feels like class-based OO turned inside out.
If Dog and Cat are subclasses of Animal (or implement an Animal interface), for example, I might want to use a "breed" method to attempt to breed a hybrid animal with the loyalty of a cat and the stealth of a dog.
public Animal breed(Animal a, Animal b)
{
// return generic Animal
}
public Animal breed(Dog a, Cat b)
{
//return Cog
}
But at compile time, if all I know is that I have two animals to breed, Java will ALWAYS pick the breed() method with the Animal arguments, even if the run-time types will be Dog and Cat.To solve this problem in Java you have to delve into the hideous Java reflection object and litter your code with junk that you'd wish the language would figure out for you.
When you've used a language that is aware of types at run time and supports multi-method dispatch, anything else seems like it's half-baked OO.
http://achoiusa.wordpress.com/2009/08/27/exploring-c-4-0-mul...
If multimethods were a subset of static typing, then regular methods would be too. Methods are dynamic by definition: the type of the method's target is inspected at runtime and used to dispatch to the correct function entry. In Python, it's explicitly stated because the first argument of all object methods is "self". Multimethods extend this to do dynamic dispatch on all argument types, not just the target object.
Most Python object methods are declared at compile time; this doesn't make method declaration a "subset of static typing". You can add new methods to classes and objects at runtime using Python's reflection and introspection; you can just as easily add new multimethods at runtime using this framework.
Finally, the @decorator(...) syntax at compile time is just syntactic sugar for something like the following:
def _foo(self, arg):
...
foo = decorator()(_foo)
So you can use decorators at runtime whenever you want too.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.
In general though, I have no idea why Python doesn't have a switch or case statement of some sort.
case [typeof(X) for x in X] of:
[int, int]:
...
[int, float]:
... { (int, int): methodWithTwoInts,
(int, float): methodsWithIntAndFloat,
...
}[ tuple([typeof(x) for x in X]) ]()Some advanced dispatch code can notice that some tuples are dispatched more often that the rest and can check for them first.
Ditto for dict lookup.
But this would unexpected for switch code.
>>> def first():
... print "a"
...
>>> def second():
... print "beta"
...
>>> caseof = {1: first, 2: second}
>>> caseof[1]()
a
>>> caseof[2]()
beta
>>> { 1: runOne, 2: runTwo, 3:runThree }[n]()
is equivalent to switch(n) {
case 1: runOne(); break;
case 2: runTwo(); break;
case 3: runThree(); break;
}That's totally possible in Python, too, but I don't really ever see it done.
dispatch = defaultdict(default_stategy)
dispatch['one'] = new_strategy
print dispatch['two'] => default_strategy.
Beats case statements and elif chains.
dispatch.get('two', default_strategy)
But my opinion is that it wouldn't hurt to have it there.
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?