[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