Why is Python popular, while Lisp and Scheme aren't?

Alex Martelli aleax at aleax.it
Thu Nov 21 09:52:49 EST 2002


Patrick W wrote:

> Alex Martelli <aleax at aleax.it> writes:
> 
>> > (defclass thing () ())
>> > (defclass paper (thing) ())
>> > (defclass rock (thing) ())
>> > (defclass scissors (thing) ())
>> > 
>> > (defgeneric beats? (thing thing))
>> > (defmethod beats? ((x thing) (y thing)) nil)
>> > (defmethod beats? ((x paper) (y rock)) t)
>> > (defmethod beats? ((x rock) (y scissors)) t)
>> > (defmethod beats? ((x scissors) (y paper)) t)
>> 
>> Multimethods are indeed very powerful, but it doesn't show "in the small"
>> (quite typical for many very powerful things)
> 
> Right.  I only intended to hint at a general concept that isn't widely
> known outside of the Lisp and Dylan communities.  It becomes *much*
> harder to 'fake' multiple dispatch (multiple polymorphism?) in more
> elaborate systems.

I'm not in either of those communities, and multimethods are the only
thing that sometimes make me wish I were -- so, they're not as little
known as all that.  And yes, in the general case faking multimethods
in a single-dispatch language is just as clunky as faking multiple
inheritance in a single-inheritance language, or faking OO dispatching
in a language that has no intrinsic OO mechanisms.


> I think Lisp's multimethods are a decent example of how macros can
> simplify a system in the long run.  To graft support for multimethods
> onto most languages would be major surgery -- but in Lisp it's just a
> bunch of macros that extend Lisp in Lisp, leaving you with Lisp.

Macros let you design a different language on top of whatever language
you have (that supports full-power macros).  That's my major beef with
them, and ultimately the reason I'm not in the Lisp or Dylan communities,
as above noticed.  I don't WANT to use a language that lets me redesign
it -- worse, I most definitely don't want to use a language that lets
_others_ redesign it, and find myself stuck using and supporting half
a dozen different (and mostly badly designed) languages in as many
disparate projects.  If my purpose in life was to experiment with
programming languages, I would surely feel differently; but it's not.


>> class thing: pass
>> class paper(thing): pass
>> class rock(thing): pass
>> class scissors(thing): pass
>> 
>> def beats(x, y):
>>     def are(X,Y): return isinstance(x,X) and isinstance(y,Y)
>>     return are(paper,rock) or are(rock,scissors) or are(scissors,paper)
> 
> Nifty ;-)

Yes, a little nested function taking class objects as parameters and
accessing the instance variables from its outer scope works nicely
here.  Of course, the limit of this approach comes when somebody
"from the outside" wants to extend it non-invasively -- as I coded
it, I've left no "hook" at all for such extensibility.  The Lisp
version above, on the other hand, makes it no problem for some third
party to add e.g. another class and the needed defmethod's to extent
the generic 'beats?' predicate to let the new class work with all
existing code.  To get such extensibility in Python I'd have to work
hard, e.g.:

class beats:
    beatpairs = (paper, rock), (rock, scissors), (scissors, paper)
    def __call__(self, x, y):
        for X, Y in self.beatpairs:
            if isinstance(x,X) and isinstance(y,Y): return True
        return False
    def addpair(self, X, Y):
        self.beatpairs = self.beatpairs + (X,Y)

now I could have several instances of "beats", each callable, all
sharing the three basic "A beats B" pairs of classes but also each
separately extensible by .addpair calls to model different sets
of rules -- but I've had to refactor the code for that, and it
has increased from three lines to eight.  Further, the decision
rule is still hardwired here to deal with class membership only,
which is why it's been relatively easy to switch the overall
organization to data-driven; in the Lisp example, one could have
a defmethod for certain specific classes that doesn't just return t or nil 
but goes on to compute whatever more complicated expression.  To
get THAT flexibility, I'd have to complicate class 'beats' yet more,
e.g include a third item in each 'pair' (spanish-inquisition time:-):

class beats:
    beatpairs = (paper, rock, 1), (rock, scissors, 1), (scissors, paper, 1)
    def __call__(self, x, y):
        for X, Y, z in self.beatpairs:
            if isinstance(x,X) and isinstance(y,Y):
                if callable(z): return z(x,y)
                else: return z
        return False
    def addpair(self, X, Y, what=1):
        self.beatpairs = self.beatpairs + (X,Y,what)

so I could somebeat.addpair(aclass, another) to mean than an instance
of aclass 'beats' one of another unconditionally (when evaluated by
the criteria in somebeat), or somebeat.addpair(Ba, Bo, pp) to mean
that for instances of Ba and Bo the 'beating' or otherwise is to be
decided by calling pp (which must be callable, to mean that:-) with
the instances as arguments.  And once again that's a small further
notch in complication which I'd be paying for a flexibility gain.


So, I do miss multiple dispatching, which would let me avoid this
infrastructure building just like I can avoid it in the more common
single dispatch case.

But I don't miss it ENOUGH to wish that Python had macros (shudder)...:-).
Not by a long shot, in fact...


Alex




More information about the Python-list mailing list