[Python-3000] Abilities / Interfaces: why not adapt()?

Phillip J. Eby pje at telecommunity.com
Tue Nov 21 18:43:27 CET 2006


At 08:24 PM 11/21/2006 -0800, "Guido van Rossum" <guido at python.org> wrote:
>Quick summary: using generic functions, you can trivially implement
>adapt() or anything like it -- and lots of other things. Either way,
>interfaces or abilities add hugely to the usability -- without them,
>you're bound to dispatch only on concrete classes, which have severe
>limitations for this purpose. There's a reason Zope has interfaces
>*and* adapt -- they go together. So do generic functions and
>interfaces.

Let's ponder that a moment.  Do you really need interfaces?

Suppose that I want to write some code that behaves differently for 
string-like things and list-like things.

In the current case, I could write code that does 'if isinstance()' for the 
two behaviors, and anybody using my code is screwed.

In the generic function case, I write a generic function that subsumes the 
'if', and anybody can define their type as string-like or list-like by 
reusing my existing 'str' and 'list' methods in the generic 
function.  Using a hypothetical generic function API, let's suppose my 
generic function is "flatten", and I want to add UserList to it, I might do 
something like:

     flatten[UserList] = flatten[list]

And that's my "interface" declaration, at least for that one operation.

Okay, so maybe you're thinking, "well, we'd want Userlist to be usable for 
every operation I write that wants to accept 'list-like' objects."  Okay, 
so I write something like this to cover all the generic operations I'm 
providing in my package:

     def like(newtype, oldtype):
         flatten[newtype] = flatten[oldtype]
         foo[newtype] = foo[oldtype]
         ...

And then call 'like(UserList, list)'.  So now you ask, "well, I want it to 
be listlike *globally*", and I say, "Really?"  What does that mean, exactly?

Well, consider len().  Isn't it just another generic function?  I mean, if 
you write code that simply uses Python's builtin and stdlib generics, then 
it suffices to make your new type work with those generics in the first 
place.  If my default "flatten()" method simply uses those generic 
operations, then any type that isn't declared to do something different, 
will "just work" if it supports the operations, and won't otherwise.

We don't really want to encourage introspectable interfaces, because that 
leads to unnecessary LBYL-ing.  Generic functions provide a kind of 
"type-safe" duck typing, because they do not rely on similarity of method 
names, but rather on explicit registration with a known generic 
function.  Thus, explicit introspection just duplicates the work of the 
generic function *while making it impossible for anybody to override the 
behavior*.  (Because you've gone back to using an if-then, which can't be 
extended by outside code!)

Therefore, if we do have an interface mechanism for use with generic 
functions, it should merely be a convenient way of *copying methods*; a 
more formal way of doing the 'like()' operation I described above.  We 
should avoid providing direct introspection of interfaces, because it is 
always possible to introspect for the specific operations you need (e.g. 
iterability, length-ability) by querying the relevant generic functions.

For example, one could have a 'hasattr' analog 'hasop(f,t)', such that 
'hasop(iter,list)' returns True.  If hasop() is a generic function, then 
user-defined generic function types (e.g. RuleDispatch-based ones) are 
equally usable.

(Some of what I've described of course would need more fleshing out for 
multiple-dispatch use cases, but single-dispatch is by far and away the 
most common use case, as can be seen from Python's existing generics.)



More information about the Python-3000 mailing list