[PEAK] Re: PEAK-rules question

PJ Eby pje at telecommunity.com
Mon Sep 2 20:32:58 EDT 2013


On Sun, Sep 1, 2013 at 4:07 PM, Sébastien de Menten <sdementen at gmail.com> wrote:
> Hello,
>
> After some research on the topic, I found a simple way to do what i wanted
> with macropy.
>
> I define the following macro in a module
>
> # macro_module.py
> from macropy.core.macros import Macros, Str, unparse
>
> macros = Macros()
>
> @macros.expr
> def s(tree, **kw):
>     return Str(unparse(tree))
> #===========
>
> and then use it as:
>
> # test.py
> from macro_module import macros, s
> from peak.rules import abstract, when
>
> @abstract
> def foo(a,b):
>   pass
> foo.when = lambda condition: when(foo, condition)   #<== this could be
> better implemented in the abstract decorator

Better still might be to implement @when() and other such APIs as
macros that transform just the second argument; that way your code
would not need the extra "s" function.  Still, it's definitely a way
around the IDE issues, and doesn't suffer from the usual issues of
access to source code (I assume, anyway).


> For my second question, explaining the real domain space would need pages so
> I must find a 'to-the-point" analogy that captures the gist of the problem.
> So here is one that does not use domain-specific terms but maps 100% with
> the problem.
> I have different kind of cars (pickup, sedans, convertible, etc) which have
> different transmissions (4x4, traction, propulsion) and different tires (all
> season, snow, high performance, etc) and all combinations of Car x
> Transmission x Tires are possible.
> Now the generic functions I want to write are like :
>  - "optimal_acceleration(car, weather)"
>  - "maintenance_planning(car)"
>  - "customer_match(car, customer_profile)"
> These functions may depend on the Car(Transmission, Tire) combination and
> while all may not be supported, I want to be able to add support for any of
> them. Moreover, if a user adds a new kind of transmission, or car, or tire,
> I want to be able to add the functions for these new combinations (if they
> make sense ... otherwise, an exception NoApplicableMethods is perfect !

Sounds just like what PEAK-Rules is intended for.

One thing you might want to watch out for in de-structuring rules like
this (ones that depend on attributes of a parameter), is to remember
that PEAK-Rules generally applies tests to parameters before testing
attributes.  This doesn't affect rule *precedence* (as far as what's
most specific), but it does affect *evaluation order*.

For example, if all your rules for a given function read in this order:

    isinstance(car.tires, Foo) and isinstance(car.transmission, Bar)

That is, if they all check the tires before checking the transmission,
then PEAK-Rules will conservatively assume that it's not safe to
access the transmission attribute until after the tires are checked.
This limits the shapes that the dispatch tree will be built in, even
though it doesn't affect the answers given.  Under some circumstances,
this might create larger or slower dispatch trees.

In contrast, tests based directly on parameters are assumed to be
independent of each other (e.g. "param1>1 and param2>2" is assumed to
allow checking either condition first) and so can be used at any level
of the dispatch tree, and so the builder is free to use whatever shape
works best for that subtree (at a cost of a little more setup time to
decide which shape is best.)

I guess what I'm trying to say is that if you have a relatively flat
rule structure, based mainly on car, car.tires, and car.transmission,
you may find it useful to make the tires and transmissions direct
parameters, or else to deliberately vary the order they appear in your
rules, so that the rule system will have maximum freedom to construct
dispatch trees.

I wouldn't even bother to mention this, except that from your
description I don't have any way to know just how complex your
dispatch trees will be.  In general, I find that generic functions
tend to follow one of two patterns:

1. "Registry" functions -- not heavily dependent on predicates, more
dependent on types of a few key parameters, relatively simple rules
with occasional extra criteria
2. "Pattern matching" or "compiler" functions -- while one or two
parameter types may be involved, the bulk of the rules are predicates,
often deeply nested predicates on component objects; often found in
compilers or compiler-like systems (such as PEAK-Rules itself) to
pattern-match subtrees with complex conditions.

In pattern #2, evaluation order may be rather important, but the tree
size is fairly limited by the fact that most of the actual predicates
exist for only a few rules.  In pattern #1, the bulk of the indexing
is for that handful of parameters, so as long as they're
freely-orderable (i.e., can be tested independently), you can't get a
blowup of tree size.  (And in pattern #1, the tests are usually done
directly on the parameters, not on attributes or methods or formulas
using the parameters.)

>From your vague description, I can't tell if your use case is more
like #1 or #2, or some sort of hybrid.  If it's basically #1 but using
lots of independent attribute-level checks, and you have very large
rulesets, then you may want to promote the attributes to parameters
(or access those attributes in different order some of the time), in
order to allow PEAK-Rules full tree reordering freedom.  It will not
affect the answers you get, only memory usage (and possibly speed).
Even then, it's unlikely to do it in the kinds of uses I'm familiar
with, but since I'm not familiar with your case, I am being
extra-thorough and cautious.  ;-)


More information about the PEAK mailing list