[Python-3000] Adaptation: T->P vs P->P

Nick Coghlan ncoghlan at gmail.com
Mon Apr 3 15:36:53 CEST 2006


FWIW, I went back and read PEP 246. That PEP goes to great lengths to permit 
arbitrary objects to be used as protocols, with the process of checking for 
compliance being a bit of a tap dance back and forth between the object and 
the protocol. Great effort was also put into registering adaptability without 
mutating any attributes of the protocol object.

The current discussion has persuaded me that it's better to require that 
protocols be special types, themselves providing a particular interface for 
adaptation purposes, and having ultimate authority over what it means to be 
compliant to themselves (thus allowing, e.g. an IIndex protocol to consider 
anything with a zero-argument __index__ method to be compliant - the act of 
defining a special interpreter method is a fair indication that the class 
considers itself compliant with that protocol).

The fact that PyProtocols, Twisted and Zope all use special objects to define 
their interfaces is also a factor in forming that opinion. The PyProtocols 
docs mention the possibility of using arbitrary objects, and then points out 
that doing so means you miss out on most of the benefits of PyProtocols. Yet 
the usability of the entire API suffers for the sake of that genericity.

So I'd be inclined to make the interface for a protocol reasonably fat by 
including things like adapter and implementation registration as methods.

With such an approach (and class decorators), it would be straightforward to 
write:

   class Duck(object):
       """The duck class"""
       @class implements(IDuckWalk, IDuckQuack, IDuckLook)
       # ...


where implements works something like:

   def implements(*interfaces):
       def register_class(cls):
           for interface in interfaces:
               interface.register_type(cls)
       return register_class

The protocol itself can preregister known good classes in its class definition.

Third parties that know a particular class implements a particular protocol 
can simply register that class directly.

Tim Hochberg wrote:
> In this thread, Alex has been advocating adaption where types are 
> adapted to protocols: T->P adaption for short. By contrast, my two 
> sample implementations have involved Protocol->Protocol adaption where 
> objects that implement a certain protocol are adapted to another 
> protocol: P->P adaption for short. I suppose this also could be 
> considered T->P->P adaptation since you look up the protocol from the 
> type, but let's stick with the terminology P->P adaption.
> 
> Alex has been advocating for adaption for quite a while (I imagine it 
> seems like forever to him), so I give his views here great weight. 
> Still, something about T->P adaption has been bothering me, but until 
> now I haven't been able to put my finger on it beyond a vague sense that 
> pressing concrete types into service in this way is asking for trouble.
> 
> Here's the problem I have with T->P adaption: it increases coupling 
> between the various clients of the adaption process. Lets talk about 
> these clients, I believe Alex said there were four:
> 
> 1. The author of the type: T
> 2. The writer of the adapter: A
> 3. The person defining the destination protocol: P
> 3. The user of the whole shebang: U
> 
> Now under a T->P regime, T needs to search out all relevant adapters and 
> register them for their type. Similarly when adding a new adapter, A 
> needs to search out all relevant classes and register the new adapter 
> for them. Thus A and T become highly coupled.

This misses the whole point of dynamic adaptation. T and P might define a few 
convenience adaptations (e.g. to or from standard library interfaces), but A 
and U will usually be the same person. Suppose framework X produces a Wibble, 
and framework Y expects an IWobble in various places. The integrator (U) needs 
to plug them together. If Wibble provides the right interface, U can simply write:

   IWobble.register_type(Wibble)

Or, more commonly, U may need to write an adapter:

   class WibbleAsIWobble(object):
       def __init__(self, the_wibble):
           self.the_wibble = the_wibble
       # delegate the IWobble API to the Wibble instance

   IWobble.register_type_adapter(Wibble, WibbleAsIWobble)

Either way, after either conformance or the adapter have been registered, a 
Wibble can be used seamlessly anywhere an IWobble was expected.

On a completely different note, now that I've researched things a bit further, 
I think PyProtocols really has done a good job thrashing out the issues 
associated with adaptation. The *only* thing I really disagree with in 
PyProtocols is that I think the API usability has suffered due to the fact 
that state is spread out amongst not only protocol objects, but is also 
sometimes stored on the types being adapted.

This leads to all sorts of "well sometimes you can do this, but sometimes you 
can't, depending on what you're adapting, so maybe you shouldn't rely on it" 
caveats. If the protocol API itself was richer (mandating that protocol 
objects support dynamic registration), then the API could be simplified 
significantly (as most operations would simply become method invocations on 
the target protocol, and the source objects could be whatever you wanted, 
since the state would all be on the protocol objects).

Regards,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://www.boredomandlaziness.org


More information about the Python-3000 mailing list