[PEAK] Generic function status update (was Re: API for generic
functions?)
Phillip J. Eby
pje at telecommunity.com
Wed Nov 10 19:17:11 EST 2004
Basic single and multi/predicate dispatch generic functions are now ready
for use in PyProtocols and PEAK CVS. They do not support advanced method
combining, but basically all the other promised features (minus C speedups,
and lambda/listcomps in rules) are now available. There have been some
minor changes to the last API proposal API, as you will see below.
The executive summary? The portions of the generic function API that are
described in this message are now at at least alpha stability, and you can
begin experimenting with them if you like. At some point I will add
advanced method combination and C speedups, but you don't need those things
to do all sorts of cool things with generic functions. I intend to go back
into PEAK for a while and start using generic functions to 1) replace
single-method interfaces in selected cases, 2) begin implementing advanced
features like conditional views for 'peak.web', refactoring 'peak.security'
to be generic function-based, and building other metadata registries on
generic functions. (Having some of these things written will help
establish performance parameters for deciding what parts of the system
should have Pyrex/C versions.)
At 09:44 PM 11/8/04 -0500, Phillip J. Eby wrote:
> [dispatch.generic(summarize=sum)]
> def priority(ctx, job):
> """Determine priority of 'job' by summing applicable scoring rules"""
>
> [priority.when("job.isRush()")]
> def rush_priority(ctx,job):
> """Add 20 to the priority for a rush job"""
> return 20
>
> [priority.when("job.owner.name=='Fred'")]
> def we_like_fred(ctx,job):
> """Add 10 more for people we like"""
> return 10
This is now implemented in CVS, except that 'dispatch.generic()' only
accepts a method-combiner function.
>Meanwhile, single-dispatch generic functions might be defined thus:
>
> [dispatch.single('source')]
> def getStreamSource(source,ctx):
> """Return a 'naming.IStreamSource' for 'source' and 'ctx'"""
>
> [getStreamSource.when(naming.IStreamSource)]
> def already_a_source(source,ctx):
> return source
>
> [getStreamSource.when([str,unicode])]
> def lookup_stream_URL(source,ctx):
> # etc...
This ended up being called 'dispatch.on("argname")' rather than
'dispatch.single("argname")', but otherwise it works as shown.
> class NormalRules:
>
> [dispatch.generic(summarize=sum)]
> def priority(self, job):
> """Determine priority of 'job' by summing applicable scoring
> rules"""
>
> [priority.when("job.isRush()")]
> def rush_priority(self,job):
> """Add 20 to the priority for a rush job"""
> return 20
>
>
> class Favoritism(NormalRules):
>
> priority = NormalRules.priority
>
> [priority.when("job.owner.name=='Fred'")]
> def we_like_fred(self,job):
> """Add 10 for people we like"""
> return 10
This now works, too, except for my above-mentioned caveat about 'generic()'.
>I guess it's not too bad. However, what about a use case where we're not
>in the class, but want to add another case to Favoritism directly?
>
> [Favoritism.priority.when("job.owner.name=='Bob'")]
> def we_really_like_bob(self,job):
> return 100
>
>Looks okay, but it won't work right without a lot of behind-the-scenes
>implementation muck, that will also slow down access to generic functions
>used as methods, unless I write that part with Pyrex.
I decided not to implement this one. You'll have to explicitly do:
[Favoritism.priority.when("job.owner.name=='Bob' and self in Favoritism")]
def we_really_like_bob(self,job):
return 100
when you are outside a class body. Note also that any rules added to a
'dispatch.generic()' inside a class body automatically have an implicit
'and self in ThisClass' added to their condition(s). (Technically, it's
not 'self', but the first named positional argument.)
>At this point, I suppose we could get rid of 'dispatch.when()' altogether.
And I did implement this; 'dispatch.when()' is gone, as well as
'dispatch.defmethod()'. These changes were necessary to implement full
support for all standard Python argument types including positionals,
keywords, defaults, and nested argument tuples. The functions used with
'dispatch.on()' and 'dispatch.generic()' serve to set the generic
function's name, docstring, argument signature, and default argument
values. The old way of just using 'dispatch.when()' to implicitly create a
generic function didn't allow for this "function prototype" mechanism.
> That seems to work alright, so let's start looking at the advanced API
> for defining fancy method combinations.
I haven't started in on the method-combining APIs yet. They remain
important for advanced uses of generic functions, but the basic functions
now implemented should be quite usable.
Also, I implemented a somewhat tricky optimization for both kinds of
generic functions, that was basically needed in order to support efficient
argument handling, but which should also speed them up in
general. Specifically, when you use the 'dispatch.on()' or
'dispatch.generic()' decorators, you will get back an actual
honest-to-goodness Python *function* object, not an instance of a special
'dispatch' type. An actual Python function is generated, that wraps the
dispatch logic.
This lets us leverage Python's built-in argument processing (including
error messages for missing/extra/malformed arguments), not to mention
numerous "fast-path" optimizations in the Python interpreter for calling
regular functions. Last, but not least, it means that documentation tools
like pydoc and epydoc will "see" generic functions as normal functions or
methods, and therefore have a better chance of documenting them correctly.
More information about the PEAK
mailing list