typetesting, adaptation, typeclasses, ... (was Re: isinstance() considered harmful)

Alex Martelli aleax at aleax.it
Thu Jan 24 03:50:24 EST 2002


<kosh at aesaeion.com> wrote in message
news:mailman.1011858574.27078.python-list at python.org...
    ...
> way. You don't want to check every method needs to indexing, len etc when
> you work with the object all the time however if you put all those methods
in
> a base class with just pass for an implementation then you can test for an

You don't need EVERY method -- you only need the methods you need.  If
you never call x.extend, for example, why put x's author to the useless
burden of implementing extend?

Most of the time, "just try to use it that way, catch or propagate the
exceptions if any" works, and it's simple and highly Pythonic.  When you
need some kind of "atomicity of state change" (you don't want a couple
of calls to e.g. append to succeed and then a later call to extend to
fail, you need them to work all-or-none), you can try Accurate-LBYL,
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52291 .  Between
them, the simple and ALBYL approaches remove most need for type-testing
and free you to enjoy Python's wonderful signature-based-polymorphism
approach, aka, "if it quacks like a duck".

We can do better.  The dream scenario from my POV would be the passing
of http://python.sourceforge.net/peps/pep-0246.html, the Object
Adaptation PEP.  But it seems it's almost a year nobody's even been
looking at it, so I wouldn't hold my breath.

Another useful idea (whose time has not quite come yet, but might
be drawing near) we can take from Haskell's typeclasses.  A Haskell
typeclass differs from, e.g., a Java interface, in these essential
ways:
    a) a typeclass is not limited to asserting, like an interface,
        "methods A, B and C exist with these signatures".  Rather,
        it can predicate HOW a method, say A, is typically to be
        implemented in terms of the others, B and C... *and vice
        versa* -- how B is implemented in terms of A and C, etc.

        You can see this as a generalization of the Template
        Method design patternt that does not arbitrarily have to
        guess at one method as "more fundamental" than others.
        Note that this typically leaves a typeclass with 'loops
        of dependency' which must be broken to 'implement' it.

    b) a typeclass T can be asserted of an existing class C retroactively
        and non-invasively, just by supplying implementations of
        some subset of T's methods in terms of C's methods (and/or
        other functions etc), such that all loops of dependency
        are broken.

In Haskell, statically typed and compiled, it's the compiler that
takes on the burden of verifying b).  In Python, it would be a
very natural role of the object-adaptation infrastructure that
must underly the proposed function adapt() of PEP 246.  To
summarize PEP 246, one would do:
    objectouse = adapt(objectpassed, thetypeclassneeded)
getting a TypeError of adaptation is not feasible, the
objectpassed itself if it already meets the protocol of the
typeclass, and some suitable wrapper or augmentation thereof
if it can be made to meet thetypeclassneeded but only that way.

Let me give a tiny example of typeclass use, in the "list
protocol" context.  We might want to do something like:

class likealist:
    def append(self, item):
        self.extend([item])
    def extend(self, sequence):
        for item in sequence:
            self.append(item)

and so on.  We need some more cleverness to add further methods
"the right typeclass way", e.g. to say that if extend exists,
__add__ can be just like extend, and, vice versa, if __add__
exists, then extend can be just like it.

But I hope the point is clear even in this toy-level example.
As 'likealist' stands, I could 'inject' it right into any
existing instance of a classic-class, e.g.:

def bruteforce_adapt(aninstance, atypeclass):
    theoldclass = aninstance.__class__
    anewclass = new.classobj(theoldclass.__name__,
        (theoldclass,atypeclass), {})
    return new.instance(anewclass, aninstance.__dict__)

or, somewhat less brutally [e.g. return aninstance unchanged
if already isinstance(aninstance, atypeclass)], but that's
not the issue.  Rather, the issue is that, if aninstance
implements neither append nor extend methods, still the
bruteforce_adapt(aninstance, likealist) "succeeds"... and
gives me an object that does implement both methods in an
unending, and quite useless, mutually recursive way.  The
first call to either method will burn a few cycles, then
raise a RuntimeError for "maximum recursion depth
exceeded"... not very productive.  In this "brute force"
approach, there is no checking that the "loops of dependency"
are indeed broken by 'concrete' methods, so that TypeError
can be raised if adaptation is unfeasible.


I think Python already has all needed tools to implement
some rough version of typeclasses -- although it will remain
a sterile exercise without PEP-246 like infrastructure to
hang it on.  PEP 246 the kind of thing that only becomes useful
if some libraries and frameworks start using it... and they
won't unless it's accepted in core Python.  Oh well!


Alex






More information about the Python-list mailing list