[Python-Dev] *Simpler* string substitutions

Alex Martelli aleax@aleax.it
Fri, 21 Jun 2002 22:37:21 +0200

On Friday 21 June 2002 07:52 pm, David Abrahams wrote:
> Such a strong endorsement from you made me go take a cursory look; I think
> I'd be -1 on this in its current form. It seems like an intrusive mechanism
> in that it forces the adapter or the adaptee to know how to do the job.

That's point (e) in the Requirements of the PEP:

e) When the context knows about the object and the protocol and
       knows how to adapt the object so that the required protocol is
       satisfied.  This could use an adapter registry or similar

but the reference implementation doesn't go as far as that -- the only
thing the PEP has to say about how to implement "third-party, non-invasive
adaptation" is this vague mention of an adapter registry.  As I recall
(the PEP's been stagnant for over a year so my memory of it is getting
fuzzy), I had gently nudged Clark Evans at the time about committing to
SOME specific form of registry, even if a minimal one (e.g., a dictionary
of callables keyed by the pair (protocol, typeofadaptee)).

However, do notice that even in its present form it's WAY less invasive
than C++'s dynamic_cast<>, which ONLY allows the _adaptee_ to solve things --
and in a very inflexible way, too.  With dynamic_cast there's no way the 
"protocol" can noninvasively "adopt" existing objects, nor can an object
have any say about it (e.g. how to disambiguate between multiple
inheritance cases).  QueryInterface does let the adaptee have an explicit
say, but still, the adaptee is the only party consulted.

Only Haskell's typeclass, AFAIK, has (among widely used languages and
objectmodels) a smooth way to allow noninvasive 3rd party post-facto 
adaptation (and another couple of small gems too), but I guess it has an 
easier life because it's compile-time rather than runtime.  Reussner et al
and of course Yellin and Strom (ACM Transactions on Programming Languages and 
Systems, 19(2):292-333, 1997) may have even better stories, but I think their
work (particularly the parts on _automatic_ discovery/adaptation) must
still count as research, not yet suitable as a foundation for a language to
be widely deployed.  So let's not count those here.

> Given libraries A and B, can I do something to allow them to interoperate
> without modifying them?

With the reference implementation proposed in PEP 246 you'd only have
a few more strings to your bow than in C++ or COM -- enough to solve
many cases, but not all.  The adapter registry, even in its simplest form,
would, I think, solve all of these cases.

> Conversely, is there a reasonably "safe" way to add adaptations to an
> existing type from the outside? I'm thinking of some analogy to
> specialization of traits in C++, here.

If a type is DESIGNED to let itself be extended in this way, no problem,
even with the reference implementation.  Remember that one of the
steps of function adapt is to call the type's __conform__ method, if
any, with the protocol as its argument.  If the PEP was adopted in its
current reference-form (_without_ a global adapter registry), there would
still be nothing stopping a clever type from letting 3rd parties extend its
adaptability by enriching its __conform__ -- most simply for example by
having __conform__ as a last step check a typespecific registry of
adapters.  Code may be clearer...:

class DummyButExtensibleType(object):
    _adapters = {}
    def addadapter(cls, protocol, adapter): 
        cls._adapters[protocol] = adapter
    addadapter = classmethod(addadapter)
    def __conform__(self, protocol):
        adapter = self._adapters.get(protocol)
        if adapter: return adapter(self, protocol)
        raise TypeError

BTW, I think the reference implementation's "# try to use the object's 
adapting mechanism" section is flawed -- it wouldn't let __conform__
return the object as being conformant to the protocol if the object
happened to be false in a boolean context.  I think TypeError must
be the normal way for a __conform__ method (or an __adapt__ one)
to indicate failure -- we can't reject conformant objects that happen
to evaluate as false, it seems to me.