[Python-3000] Sane transitive adaptation
Nick Coghlan
ncoghlan at gmail.com
Sat Apr 8 06:21:26 CEST 2006
Guido van Rossum wrote:
> On 4/7/06, Nick Coghlan <ncoghlan at iinet.net.au> wrote:
>> I've been stealing liberally from PyProtocols all along, usually trying to
>> grasp ways to take *concepts* from it that I liked, and turn them into
>> something that seemed *usable* (with aesthetics playing a large part in that).
>
> Perhaps you could give us a hint on where the parts of PyProtocols
> that you like are documented on the web? Scanning the
> peak.telecommunity.com site I always seem to get lost. For example, I
> have so far had no luck finding either the source code or the docs for
> RuleDispatch there. I got tantalizingly close when I found a reference
> to a module named 'dispatch' in core.py in the peak svn tree, but
> there was nothing named dispatch or dispatch.py nearby... :-( The "API
> docs" similarly apear as a forest of dead leaves to me.
Mostly just the API docs:
http://peak.telecommunity.com/protocol_ref/module-protocols.html
This was the specific page about using a concrete API on the protocols themselves:
http://peak.telecommunity.com/protocol_ref/protocols-calling.html
>
>> That's why one of the first things I hit on in rereading PEP 246 was "hang on,
>> why are we use a pair of ad hoc protocols to build an adaptation system"? And
>> lo and behold, PyProtocols had a much easier to use system based on concrete
>> protocol objects, where you manipulated the target protocol directly.
>>
>> And my last message about adaptation, which was a (massively) simplified
>> version of some of the ideas in PyProtocols, let me understand how
>> transitivity would apply in a generic function context: you have function_A
>> with a certain signature, and existing operations and function_B that can be
>> implemented in terms of function_A.
>
> This is clear as mud to me.
It's not really that important, except in convincing me personally that
overloadable functions really are a superset of protocol adaptation.
Another way of looking at it:
1. Suppose function A and function B do the roughly the same thing
2. Suppose function A can be overloaded/extended
3. It would then be straightforward to add additional extensions to
function A that delegated certain call signatures to function B (possibly
with a wrapper to deal with slight differences in the order of arguments or
the type of the return value.
Turning that into *transitivity* is a matter of asking the question, "well ,
what if B can be overloaded as well?". The idea then is that you would pass B
a callback to be invoked whenever a new signature was registered with B. The
callback would take the new signature, rearrange it if necessary, and register
the new version with function A.
@function_B.call_for_new_registrations
def extending_B_extends_A_too(signature, extension):
# The decorator means this function would be called whenever a new
# extension was registered with function B
# This particular callback implements the rule "any extension of B
# is automatically an extension of A as well"
function_A.register(signature)(extension)
(using PJE's terminology here, because I'm not sure of the right word for the
extending functions when the concept is called "overloading")
>> I like being able to use 'signature' and 'specialization' as the terms, too,
>> as they describe exactly what's going on.
>
> Not to me. I think of "signature" as a tuple of types, and maybe
> that's what you mean it to describe. But what's a specialization? Is
> it a callable? Is it a string? What properties does a specialization
> have?
>
> (This reminds me of the kind of conversation I sometimes have with
> Orlijn. Sometimes he'll say "I saw a frizzlefrazzle today!" I'll say
> "what's a frizzlefrazzle?" He'll say something that sounds to me like
> "Not frizzlefrazzle, frizzlefrazzle!" After a few tries I give up, and
> I have to change my line of questioning to get him to describe a
> frizzlefrazzle. The most effective questions are usually "what does a
> frizzlefrazzle do?" or "what does a frizzlefrazzle have?" That's what
> I'm asking you here. :-)
A specialization would be a tailored version of the function registered to
deal with a particular signature.
An adapter in protocol terms, an extension in PJE's terms, and, as noted
above, I'm not sure what to call it when the concept is referred to as
function overloading :)
>> Further, if all builtin function
>> objects (including C function descriptors and method wrappers) were
>> potentially generic,
>
> This seems a rather extreme anticipation. I don't think anybody has
> assumed this is where we might be going.
There's certain attractive properties to doing it though - if we can get it
down to the point where the runtime hit is minimal for non-extended functions,
then it avoids the scenario "if only framework X had made function Y
extensible, the fixing this would be easy!".
OTOH, if there's an unavoidable call time cost, then a trivial @extensible or
@overloadable decorator would do the trick.
> Forgetting that str is really a type; assuming str were an @overloaded
> function (I'm sticking to my terminology :-) then I could have written
> that as
>
> @str.register(SomeClass)
> def str_SomeClass(obj):
> return obj.stringify()
>
> I'm not sure though what @specialize(str) adds to this.
>
> Assuming we can't change str into an @overloaded function but we do
> want to allow this style of overriding, I guess we could have some
> external registry object which str consults before looking for a
> __str__ method or using hardcoded knowledge.
And that's exactly where the benefits of having an "overload" or "extend"
function comes in. Using PJE's terms, we might write:
_cls_extensions = {}
class ClassRegistry(object):
"""This would expose all the methods of an extensible function"""
def __init__(self, cls):
self.cls = cls
self.registry = {}
def for_signature(self, *signature):
# This is the same as register()
# but reads better with the preceding function call
def helper(f):
self.registry[signature] = f
return f
return helper
@extend(extend).for_signature(type)
def get_registry(cls):
# This function retrieves the extension registry for a class
try:
registry = _cls_extensions[cls]
except KeyError:
registry = _cls_extensions[cls] = ClassRegistry(cls)
return registry
With a change in type.__call__() to check the class extension registry
before calling cls.__new__(), the above would put us well on the way to making
all types extensible. For example, we could make a class of our own behave
like a dictionary, returning its keys when converted to a list, and its
key-value pairs when converted to a dictionary:
@extend(list).for_signature(MyMapping):
def get_keys(obj)
return obj.keys()
@extend(dict).for_signature(MyMapping):
def get_items(obj)
return obj.items()
Cheers,
Nick.
--
Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia
---------------------------------------------------------------
http://www.boredomandlaziness.org
More information about the Python-3000
mailing list