multi-dispatch (was Re: large class hierarchies in python)

Alex Martelli aleax at aleax.it
Wed Sep 12 09:19:52 EDT 2001


"Justin Dubs" <jtdubs at eos.ncsu.edu> wrote in message
news:9nm5ev$pfr$1 at uni00nw.unity.ncsu.edu...
> "Alex Martelli" <aleax at aleax.it> wrote in message
> news:9nksof021p1 at enews3.newsguy.com...
> > "Justin Dubs" <jtdubs at eos.ncsu.edu> wrote in message
> > news:9nismr$fve$1 at uni00nw.unity.ncsu.edu...
>  ...
> > A brief tirade in favour of multi-dispatching...:
> >
> > It's runtime-polymorphism over an unlimited number of objects, rather
> > than over one object at a time.  In any language that doesn't offer
> ...
> > And the Dylan implementation looks fine to me in as far
> > as I've dug into it.  What do you find wrong with it...?
>
> Ahhhhhh..... Thank you.  I didn't spend enough time letting the concepts
> sink in.  Thanks for the great explanation.  It makes sense now.  That is
> very useful.  I still hate the dylan syntax, but the multi-dispatching
makes
> sense now.  Thank you for the help.

You're welcome!  Another characteristics of Dylan that we
see in Python 2.2 at last (although I'm not sure whether
Guido was aware of it or simply redesigned it anew) is a
cleverer ordering of bases in a multiple-inheritance case
where one or more base classes happen more than once (an
"inheritance diamond" or similar topologies), with support
for "walking" along bases classes in a systematic way
(although the latter, with the 'super' built-in, is still
rather syntactically unripe in Python 2.2 alpha 3, there IS
hope that it may be better integrated before 2.2 gets
released).

Let's give a classic example in Pythonic syntax (I share
your dislike for Dylan's syntax, though it falls well
short of "hate" in my case:-)...:

class Base:
    def methodone(self): print "Base::methodone"
    def methodtwo(self): print "Base::methodtwo"

class BetterOne(Base):
    def methodone(self): print "a better methodone"

class BetterTwo(Base):
    def methodtwo(self): print "a better methodtwo"

class BetterBoth(BetterOne, BetterTwo): pass

bb = BetterBoth()
bb.methodone()
bb.methodtwo()


Looks reasonable, right?  Alas, in Python 2.1 and
earlier:
a better methodone
Base::methodtwo

The order in which classes are looked up when
searching for any bb attribute is:
    BetterBoth, BetterOne, Base, BetterTwo
so Base.methodtwo HIDES the BetterTwo.methodtwo
we _thought_ we had overridden.

The identical sources still give the same
disapponing result in Python 2.2, for the usual
reason -- backwards compatibility.  BUT just
change the first line to

class Base(object):

[or, any other built-in type can be used as
Base's base, but 'object' is, well, "neutral"...]
so that Base and its descendants don't have to
be "classic classes" anymore (with their many
backwards-compatibility restrictions), and...:

C:\>\python22\python bb.py
2.2a3 (#23, Sep  7 2001, 01:43:22) [MSC 32 bit (Intel)]
a better methodone
a better methodtwo

...exactly as one would hope!


The old ordering of class lookup can be
seen as left-first, depth-first, period.
Strictly speaking there's an extra Base
in the linearized lookup-ordered sequence
of bases, at the end (after BetterTwo),
but nothing is ever found there of course
(since an earlier instance of Base in the
sequence was searched before during the
lookup).

The new order can be seen as follows: start
with the old left-first, depth-first one:

BetterBoth, BetterOne, Base, BetterTwo, Base

but now, if any class appears more than once,
only keep the LAST appearance (while the old
"classic classes" rule boiled down to keeping
the FIRST appearance), to get:

BetterBoth, BetterOne, BetterTwo, Base

and THAT is exactly what we want in just about
all cases!  It's also Dylan's algorithm, although
their way to frame things is different.


This "mro" (which I believe stands for "method
resolution order") is built ONCE when a class is
defined, for performance and clarity reasons
I guess, so we do lose in terms of dynamic
behavior (not for classic classes though:-).
But the gain is big, even aside from any kind
of performance considerations.  I guess I
shall be inheriting more and more of my classes
from object, unless some other builtin type
appears more suitable:-).  Apart from overriding
the right things, it also makes possible (via
super) the correct way of cooperation under
multiple inheritance, where a class may do a
bit of extra work in a method it overrides but
wants to delegate most of it to superclasses --
the super builtin makes it possible (currently
only with some syntactic kludgeing around:-) to
delegate to "whoever comes after me in mro",
which may be different classes in different multi
inheritance cases...

(Do note that Dylan has had the equivalent of
this for quite a few years:-).

If you do look into Dylan, one of its features
which I _don't_ like (an extensive, rich macro
framework) may perhaps help you work around the
syntax you detest so much.  It's worth it for
mind-expansion purposes, I think, even if you
put it back on the shelf when you're done
absorbing its lessons -- just as, say, for
Haskell or Erlang.  Gwydyon Dylan should be
OK for Macs, Unix/Linux/BSD, or Cygwin, while
funpro makes a subset of their commercial
offering (the complete language, just not the
rich extra libraries for COM access &c &c)
available for free under Windows.  Who knows,
maybe if more Pythonistas get used to (and
fired up about:-) such features as multi-dispatch
and optional-typing, one day they'll be adding
them into the Python mainstream and we'll enjoy
those AND the joys of Python at once, just as
soon with Python 2.2 we'll be doing for clever
class ordering in multi-inheritance cases!-)


Alex






More information about the Python-list mailing list