[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