adaptation

Alex Martelli aleaxit at yahoo.com
Tue Nov 16 13:45:29 EST 2004


Carlos Ribeiro <carribeiro at gmail.com> wrote:
   ...
> Well, my name is not Alex, and my answer will probably fall short of a
> comprehensive definition :-) But let's see if I can help here...

I think you did a great job!  May I recommend some further reading...:

http://www.python.org/peps/pep-0246.html
http://www.aleax.it/os04_pydp.pdf
http://peak.telecommunity.com/protocol_ref/module-protocols.html


> Adaptation is the act of taking one object and making it conform to a
> given protocol (or interface). Adaptation is the key to make dynamic
> code that takes parameters from arbitrary types work in a safe, well
> behaved way.

Hear, hear!

> The basic concept underlying adaptation is the "protocol", also called
> "interface" in some implementations. For all purposes of this
> discussion, and for simplicity reasons, we can safely assume that
> protocols and interfaces are equivalent.

Right, but, for the curious...: "Interface" mostly describes a certain
set of methods and their signatures; "Protocol" adds semantics and
pragmatics -- some level of conceptualization of what the methods _do_
and constraints on how they are used (e.g. "after calling .close(), no
other method can ever be called on the object any more").  This is a
reasonably popular use of the words in question, though far from
universal.


> A protocol is a set of primitives that is supported by a given object.
> For example: the iterator protocol defines the following primitives:
> __iter__ and next() (as documented in
> http://docs.python.org/lib/typeiter.html). Any object from any class
> that implement these methods, regardless of its ancestors, is said to
> support the iterator protocol.

...if the semantics and pragmatics are also respected, e.g.:
    x.__iter__() is x
    

> Any object that supports the iterator protocol can be used whenever an
> iterable is acceptable. This includes for loops and list
> comprehensions. The biggest advantage of adaptation comes when one
> realize how flexible this design is, specially when compared with
> old-style type checking. In a old-style type checking environment,
> parameters to a given routine must conform to the declared type of the
> arguments. For iterators, it would mean that only objects descending
> from some abstract class (let's say, "Iterable") would be accepted.
> Now, even with multiple inheritance, this design is severely limited.

In old-style Python, inheritance isn't really the issue (except for
exceptions, where inheritance does matter).  Rather, a protocol is
defined by a given set of methods.

An iterable is an object supplying a special method __iter__ which,
called without arguments, returns an iterator (any object which respects
the iterator protocol).  A sequence besides being iterable supports
__len__, AND __getitem__ with integer and slice arguments, and there is
a rich semantic and pragmatic web of mutual constraints between behavior
of __getitem__ and __len__ and iteration.  A mutable sequence is a
sequence which also supports __setitem__ (again with specific
constraints wrt __getitem__, __len__...) and is also supposed to expose
a rich set of other methods such as pop, append, extend, etc, etc.

This IS great BUT limited by what the LANGUAGE designer(s) sanction(s)
as 'blessed protocols'.  There are quite a few, but they're never enough
to cover the needs of an application or field of applications, of
course.  With protocols based on certain special methods, you have a
great concept which however is not really extensible, nor flexible
enough to help the authors of large frameworks and applications.

Framework authors do define new protocols, of course -- they can't help
doing that.  "X must be an object supplying methods 'foo' and 'bar' with
the following constraints...:".  This is OK for somebody who's writing
an application using just one framework -- they can code their classes
to the framework's specifications.

The problem comes in when you're writing an application that uses two or
more frameworks... the two frameworks likely haven't heard of each
other... one wants objects supplying 'foo' and 'bar, the other supplies
objects supplying 'oof' and 'rab' instead, with subtly different
semantics and pragmatics.  So, what do you do then?  You write adapters:
wrappers over Y with its oof and rab which provide an X with its foo and
bar.  Python is _great_ at that kind of job!

But, who applies the adapters, where, when?  Well, unless we do get PEP
246 in... _you_, the application writer, are the only one who can.
You'd like to spend your time developing your application, with
frameworks to relieve you from some parts of the job, but to do that you
also need to develop adapters _and_ tediously, boilerplatishly, apply
them to every object from one framework that's going over to the other,
etc.  Basically, you risk ending up with very *thick glue* (cfr Eric
Raymond's excellent "The Art of Unix Programming", great book also
available online for free) -- throughout your application, there will be
knowledge about the details of all frameworks you're using, spread in
thick layers of glue.


> Now, back to Python world. In many situations, there is no need for
> adaptation; the object itself supports the protocol, and can be
> supplied directly. But there are situations when the object itself
> can't be immediately used; it has to be adapted, or prepared, to
> support the protocol. Another situation is when an object is passed to
> a routine that *doesn't* support the required protocol; this is an
> error, that can be catched by the adapt() framework in a superficially
> similar but fundamentally different approach from type checking (and
> that's whats Alex has been pointing out).

Oh yes, VERY different.  Let me try an analogy...

A policeman's job is to ensure you respect the law.  He does that by
trying to catch you violating the law, and punishing you for that.

A civics teacher's job is to ensure you respect the law.  He does that
by teaching you the law, explaining its rationale, engaging you in
discussion to find instances where your spontaneous behavior might
violate the law, and working out together with you how to adapt your
behavior and your instincts so that the law gets respects.

Type checking is a policeman.  Adaptation is a civics teacher.


> The adapt protocol (as presented on PEP246 -
> http://www.python.org/peps/pep-0246.html) defines a very flexible
> framework to adapt one object to a protocol. The result of the

...and yet the text of PEP 246 is still missing the specs about
registering "third party adapters".  Phil Eby's PyProtocols is much
better that way!!!  (I promise I'll do something about PEP 246 updating:
just as soon as I'm done with the 2nd ed of the cookbook....!!!!).

> adaptation (if possible) is an object that is guaranteed to support
> the protocol. So, using adapt(), we can write code like this:
> 
> def myfunc(obj):
>     for item in adapt(obj, Iterable):
>         ...

Hmmm, yes, we can.  It's a standard protocol so not the best of
examples, but still, it may be meaningful.


> Finally, one may be wondering, is there any situation when an object
> needs to be "adapted"? Why don't just check for the availability of
> the interface? There are many reasons to use the adapt framework. The
> protocol checking is one of the reasons -- it allows errors to be
> catched much earlier, and at a better location. Another possible
> reason is that complex objects may support several protocols, and
> there may be name clashes between some of the methods. One such
> situation is when an object support different *versions* of the same
> protocol. All versions have the same method names, but semantics may
> differ slightly. The adapt() call can build a new object with the
> correct method names and signatures, for each protocol or version
> supported by the object. Finally, the adaptation method can optionally
> build an opaque "proxy" object, that hides details of the original
> methods signature, and it's thus safer to pass around.

The main motivation I'd give is that different frameworks not knowing
about each other may define [1] what the object supplies and [2] what
the object is REQUIRED to supply -- there are often discrepancies, and
an adapter in-between is gonna be required.  With 246 (once suitably
updated;-) we can write the adapter ONCE, register it in a suitable
global registry, and 'adapt' will just find it.  Oh bliss -- as long as
adapt DOES get called all over the place!-)

 
> Well, that's a broad overview of the subject. There is a lot of stuff
> to learn, and using adaptation properly is something that takes some
> time. Hope it helps.

My compliments for your excellent presentation!  I hope my enrichment of
it may have proved useful rather than distracting....


Alex



More information about the Python-list mailing list