One issue with generic functions and adaptation as currently being discussed 
(and something Tim mentioned a while back), it that it is very focused on 
dispatching based solely on obj.__class__.__mro__.

That's all well and good, but (to use an example of Tim's), suppose we have a 
couple of frameworks we're using, where those frameworks have each defined 
their own version of a "wibbly-wobbly" object:

   frmwrk_A.IWibble
   frmwrk_B.IWobble

The methods and signatures included in these protocols may be the same, 
IWibble may be a subset of IWobble, or vice versa.

Both frameworks have defined their interfaces (input and output) in terms of 
these protocols, rather than in terms of concrete types.

As Tim pointed out, pure type-based dispatch would require that every type be 
registered with both protocols, even if the two protocols are identical. Doing 
that manually would be a serious pain if the frameworks were non-trivial.

The first thought may be to allow adaptation between protocols - but the issue 
with that is that at adaptation time, we only have the object to work with, 
and won't know what protocols it claims to implement (this is where either 
__conform__ or a global registry comes in with PEP 246).

Even if we did know, the cost of doing a search on all equivalent protocols on 
each call would add up. We don't really want to be finding ways to make 
functions calls even slower than usual ;)

There is, however, an alternative, which would be to include a mechanism for 
telling a protocol about other equivalent protocols, and updating the 
registration mechanism to distribute any registration updates to the 
equivalent protocols.

For example (not tested code!):

   def _update(self, updates, updated=None):
       # Update ourselves with a change
       # This update does NOT overwrite existing mappings
       changes = {}
       for signature, adapter in updates.iteritems():
           if signature not in self.registry:
               changes[signature] = adapter
       self.registry.update(changes)
       self._distribute_update(changes, updated)

   def _distribute_update(self, updates, updated=None):
       # We don't want to get this update back
       if updated is None:
           updated = set()
       else:
           if self in updated:
               return
       updated.add(self)
       # Update any equivalent protocols
       # that have not yet been updated
       for protocol in self.subprotocols:
           if protocol not in updated:
               protocol._update(updates, updated)

   def _register_callable(self, signature, callable)
       # Explicit registration overrides existing mapping
       self.registry[signature] = adapter
       self._distribute_update({signature:callable})

   def register_subprotocol(self, protocol):
       # Specified protocol is a subset of this one
       # so it can safely use this protocol's adapters
       protocol._update(self.registry)
       self.subprotocols.add(protocol)

   def register(self, *key)
       def helper(adapter):
           self.register(adapter, *key)
           return adapter
       return helper

Using such a mechanism, the interface mismatch above could be addressed in one 
of the following ways:

Suppose framework A specifies fewer methods than framework B, but those 
methods match. Then you can write:

   frmwrk_B.IWobble.register_subprotocol(frmwrk_A.IWibble)
   # Now registration for framework B also registers you for the narrower
   # interface in framework A

You can turn that around, if A is the one that is more prescriptive:

   frmwrk_A.IWibble.register_subprotocol(frmwrk_B.IWobble)
   # Now it is registration for framework A that registers you for the narrower
   # interface in framework B

And finally, if the two interfaces are identical:

   frmwrk_A.IWibble.register_subprotocol(frmwrk_B.IWobble)
   frmwrk_B.IWobble.register_subprotocol(frmwrk_A.IWibble)
   # Now registration for either framework registers you for both

Now, suppose, however, that mapping from A to B required a slight tweak to A's 
interface - one of the method signatures didn't line up right (e.g. one of A's 
methods has the wrong name). This can be handled with an explicit protocol 
adapter, which would provide a modified update method like so:

   class ProtocolAdapter(object):
       def __init__(self, src, target):
           self.target = target
           src.register_subprotocol(self)

       def _update(self, updates, updated=None):
           if self.target in updated:
               return
           wrapped_updates = {}
           for signature, adapter in updates.iteritems():
               def wrapped_adapter(*args, **kwds):
                    return self.adapt(adapter(*args, **kwds))
               wrapped_updates[signature] = wrapped_adapter
           self.target._update(wrapped_updates, updated)

       def __call__(self, adapter):
           self.adapt = adapter

   class AdaptWibbletoWobble(object):
       def __init__(self, obj):
           self.obj = obj
       def __iter__(x, y, z):
           return self.obj.method2(x, y, z)

   ProtocolAdapter(frmwork_A.IWibble, frmwrk_B.IWobble)(AdaptWibbleToWobble)

The equivalent of the above for generic functions is the case where "generic 
function A" does a very similar thing to "generic function B", and you want to 
be able to do a wholesale delegation from A to B of everything that B handles 
more specifically than A.

Aside from the fact that I think any such transitivity mechanism should be 
observer based, and that the updates should happen at registration time rather 
than lookup time, I'm not really wedded to any of the implementation details 
above. But I figured it was something worth throwing out there :)

Cheers,
Nick.

-- 
Nick Coghlan   |   [EMAIL PROTECTED]   |   Brisbane, Australia
---------------------------------------------------------------
             http://www.boredomandlaziness.org
_______________________________________________
Python-3000 mailing list
Python-3000@python.org
http://mail.python.org/mailman/listinfo/python-3000
Unsubscribe: 
http://mail.python.org/mailman/options/python-3000/archive%40mail-archive.com

Reply via email to