[PEAK] Another way to spell generic function predicates

Phillip J. Eby pje at telecommunity.com
Sat Jan 1 23:21:11 EST 2005


While working on some refactoring to support an expanded API, it occurred 
to me that we could do something like:

     @predicate("len(container)==0")
     def isEmpty(container):
         """Test whether container is empty"""


and then use it in generic function tests, i.e.:

     @someGeneric.when("isEmpty(foo) and not isEmpty(bar)")
     def doSomething(foo,bar):
         """blah"""

The idea here is that the 'predicate' decorator replaces the decorated 
function's body with the supplied expression, but also marks the function 
as being a predicate function, along with how to compute the 
predicate.  Thus, the 'someGeneric.when()' would automatically be inlined 
to the equivalent of:

     @someGeneric.when("len(foo)==0 and not len(bar)==0")
     def doSomething(foo,bar):
         """blah"""

I think this is a better idea than the simple "macro" I was suggesting the 
other day, as it makes the parameterization explicit, allows multiple 
arguments, and doesn't introduce new namespaces to keep track of; it works 
just like calling a function would, only faster because the conditions get 
"inlined" -- and are therefore indexed and computed at most once per 
invocation.

The second piece is the ordered classifier thing.  I'm thinking that could 
be done by simply combining @predicate decorators with a Classifier 
subclass, e.g.:

     class Frame(Classifier):

         @predicate("isEmpty(rolls) or frame>10")
         def finished(rolls,frame):
             """Nothing left to score"""

         @predicate("rolls[0]==10")
         def strike(rolls,frame):
             """Frame was a strike"""

         @predicate("sum(rolls[:2])==10")
         def spare(rolls,frame):
             """Frame was a spare"""

         @default()
         def open(rolls,frame):
             """Frame was open"""

The idea here is that the predicates are arranged in priority order, and a 
given predicate is translated as the negation of the previous predicates, 
and-ed with the specified predicate.  So, for example, even though 'strike' 
above is 'rolls[0]==10', the *actual* strike predicate is 'not 
(isEmpty(rolls) or frame>10) and rolls[0]==10'.  And so on through the 
items, so that the default case occurs only if no other predicate 
matched.  For the scoring example, one could use the above classifer in 
this fashion:

     @dispatch.generic()
     def score(rolls,frame=1):
         """Return score for bowling game, given list of a player's rolls"""

     @score.when("Frame.strike(rolls,frame)")
     def score_strike(rolls,frame):
         return sum(rolls[:3]) + score(rolls[1:],frame+1)

     @score.when("Frame.spare(rolls,frame)")
     def score_spare(rolls,frame):
         return sum(rolls[:3]) + score(rolls[2:],frame+1)

     @score.when("Frame.open(rolls,frame)")
     def score_open(rolls,frame):
         return sum(rolls[:2]) + score(rolls[2:],frame+1)

     @score.when("Frame.finished(rolls,frame)")
     def scoring_done(rolls,frame):
         return 0

That is, predicate functions in a Classifier subclass are treated as static 
methods, and they could be inlined and optimized just like ordinary 
predicate functions.

What do you think?  This seems to me like a more straighforward way to 
spell "macros" and ordered classifications without introducing weird new 
rules and keyword arguments and namespaces.




More information about the PEAK mailing list