Ian Bicking: the old part of his blog

More on Multimethods

I think Guide is thinking about typing and whatnot a lot lately, now with a post one multimethods, which mostly the same as double-dispatch. I.e., you choose a function implementation based on the type of the arguments.

However, Guido uses a global registry of functions by name. I like Phillip's technique for generic functions, which is to give these a functions a richer interface that includes a decorator. Here's an example of Guido's code converted to that:

@multimethod
def div(a, b):
    "div a by b"
    return a / b
class rational(object):
    def __init__(self, num, denom):
        self.num, self.denom = num, denom
    def __repr__(self):
        return '%s/%s' % (self.num, self.denom)
@div.types(rational, int)
def div_rat_int(r, i):
    return rational(r.num, r.denom*i)
@div.types(int, rational)
def div_int_rat(i, r):
    return rational(r.denom * i, r.num)
@div.types(rational, float)
def div_rat_float(r, f):
    return r.num / (r.denom*f)
@div.types(float, rational)
def div_float_rat(f, r):
    return f * r.denom / r.num
@div.types(rational, rational)
def div_rat_rat(r1, r2):
    return rational(r1.num * r2.denom, r1.denom * r2.num)

Here's how it looks:

>>> half = rational(1, 2)
>>> half
1/2
>>> third = rational(1, 3)
>>> div(half, third)
3/2
>>> div(half, 2)
1/4
>>> div(half, 0.5)
1.0
>>> div(1, 2)
0
>>> div(1, half)
2/1

And here's the implementation (it's actually simpler than Guido's):

class multimethod(object):
    def __init__(self, default):
        self.default = default
        self.name = default.__name__
        self.__doc__ = default.__doc__
        self.typemap = {}
    def __call__(self, *args):
        types = tuple(arg.__class__ for arg in args) # a generator expression!
        function = self.typemap.get(types, self.default)
        return function(*args)
    def types(self, *types):
        def decorator(func):
            if types in self.typemap:
                raise TypeError("duplicate registration")
            self.typemap[types] = func
            if func.__name__ == self.name:
                # to avoid confusion by overwriting the multimethod function
                return self
            else:
                return func
        return decorator

The module is located online at http://svn.colorstudy.com/home/ianb/recipes/multimethod.py

Update: some people have mistakenly called this implementation a generic function, or a reimplementation as a generic function. It's not generic (aka predicate-dispatch) functions at all -- those are much more general (and powerful), and this version is just a slightly different factoring of Guido's code. Thankfully Bob Ippolito does it right and shows how to implement multimethods with generic functions.

Created 01 Apr '05
Modified 04 Apr '05

Comments:

Just an FYI, but if the user imports div "as" another name, this code won't catch that and return self. The technique I use in PyProtocols is to check whether the decorated function's __name__ in the caller's f_locals is self. So you could do something like:

if sys._getframe(1).f_locals.get(func.__name__) is self:
    return self
else:
    return func

Then it doesn't depend on the original generic function's definition being only accessible via its original name, in order to prevent such confusion.

# Phillip J. Eby