The PEAK Developers' Center   Diff for "PEAK-Rules/Criteria" UserPreferences
 
HelpContents Search Diffs Info Edit Subscribe XML Print View Up
Ignore changes in the amount of whitespace

Differences between version dated 2010-08-10 19:41:39 and 2011-08-31 21:11:41 (spanning 3 versions)

Deletions are marked like this.
Additions are marked like this.

 
    Test(IsInstance(Local('x')), Class(Y))
 
``Conjunction`` subclasses, on the other hand, are used to "and" together
``Conjunction`` instances, on the other hand, are used to "and" together
criteria that apply to the same dispatch expression. For example, this
expression::
 

 
would be represented internally like this::
 
    Test(IsInstance(Local('x')), Classes([Class(Y), Class(Z)]))
 
(That is, ``Classes`` is a ``Conjunction`` subclass representing the "and" of
multiple ``Class`` criteria.)
 
    Test(IsInstance(Local('x')), Conjunction([Class(Y), Class(Z)]))
 
The rest of this document describes how predicates, signatures, tests, dispatch
expressions, and criteria work together to create expressions in disjunctive

true when the original conditions apply. So, if it returns a boolean value,
that's just an indication that the intersection of the two input conditions
would always apply or never apply. Also, ``intersect()`` is logically
symmetrical, in that it doesn't matter what order the arguments in, whereas
symmetrical, in that it doesn't matter what order the arguments are in, whereas
the order is critically important for ``implies()``.
 
However, ``intersect()`` methods must be order *preserving*, because the order
in which logical "and" operations occur is important. Consider, for example,
the condition ``y!=0 and z>x/y``, in which it would be a bad thing to skip the
zero check before division. So, as we will see later on, when working with
more complex conditions, ``intersect()`` methods must ensure that the subparts
of the output condition are in the same relative order as they were in the
input.
 
(Also, note that in general, intersecting two conditions where one condition
implies the other, the result is the implying condition. This general rule
greatly simplifies the implementation of most intersect operations, since as
long as there is an implication relationship defined between conditions, many
common cases of intersection can be handled automatically.)
 
In contrast to both of these functions, the ``disjuncts()`` function takes
only a single argument, and returns a list of the "disjuncts" (or-ed-together
conditions) of its argument. More precisely, it returns a list of conditions
that each imply the original condition. That is, if any of the disjuncts were
true, then the original condition would also be true.
the condition ``"y!=0 and z>x/y"``, in which it would be a bad thing to skip
the zero check before the division!
 
So, as we will see later on, when working with more complex conditions,
``intersect()`` methods must ensure that the subparts of the output condition
are in the same relative order as they were in the input.
 
(Also, note that in general, when you intersect two conditions, if one condition
implies the other, the result of the intersection is the implying condition.
This general rule greatly simplifies the implementation of most intersect
operations, since as long as there is an implication relationship defined
between conditions, many common cases of intersection can be handled
automatically.)
 
In contrast to both ``implies()`` and ``intersects()``, the ``disjuncts()``
function takes only a single argument, and returns a list of the "disjuncts"
(or-ed-together conditions) of its argument. More precisely, it returns a list
of conditions that each imply the original condition. That is, if any of the
disjuncts were true, then the original condition would also be true.
 
Thus, the ``disjuncts()`` of an arbitrary object will normally be a list
containing just that object::

``False`` from the resulting predicate, and conditions that can never be true
are not used for indexing or dispatching.
 
Another special case is tuples containing nested tuples::
 
    >>> disjuncts( (float, (int, str)) )
    [(<type 'float'>, <type 'int'>),
     (<type 'float'>, <type 'str'>)]
 
    >>> disjuncts( ((int, str), object) )
    [(<type 'int'>, <type 'object'>),
     (<type 'str'>, <type 'object'>)]
 
    >>> disjuncts( (object, (int, str), float) )
    [(<type 'object'>, <type 'int'>, <type 'float'>),
     (<type 'object'>, <type 'str'>, <type 'float'>)]
 
    >>> disjuncts( ((int, str), (int, str)) )
    [(<type 'int'>, <type 'int'>),
     (<type 'str'>, <type 'int'>),
     (<type 'int'>, <type 'str'>),
     (<type 'str'>, <type 'str'>)]
 
This lets you avoid writing lots of decorators for the cases where you want
more than one type (or ``istype()`` instance) to match in a given argument
position. (As you can see, it's equivalent to specifying all the individual
combinations of specified types.)
 
Finally, the ``negate()`` function inverts the truth of a condition, e.g.::
 
    >>> negate(True)

can also add your own, as long as they can be tested for implication with
``implies()``, and intersected with ``intersect()``. (And if they represent an
"or" of sub-criteria, they should be able to provide their list of
``disjuncts()``.)
``disjuncts()``. They'll also need to be indexable, but more on that later in
other documents!)
 
 
"And"-ed Criteria

 
    >>> class MySet(Conjunction): pass
 
    >>> type(intersect(MySet([int, str]), float))
    <class 'MySet'>
    
    >>> intersect(MySet([int, str]), float) == MySet([int, str, float])
    True
 

    <type 'object'>
 
Notice that instead of getting back a set or sequence with one member, we got
back the item that would have been in the set. This helps to simplify the expression
structure. As a further simplification, creating an empty disjunction returns
``False``, because "no conditions are sufficient" is the same as "always
false"::
back the item that would have been in the set. This helps to simplify the
expression structure. As a further simplification, creating an empty
disjunction returns ``False``, because "no conditions are sufficient" is the
same as "always false"::
 
    >>> DisjunctionSet([])
    False

    OrElse([DisjunctionSet([1, 2]), DisjunctionSet([3, 4])])
 
(The ``disjuncts()`` of an ``OrElse`` are much more complicated, as the
disjuncts of a Python expression like ``a or b or c``" reduce to ``a``,
``(not a) and b``, and ``(not a and not b) and c``! We'll cover this more
later, in the section on `Predicates`_.)
disjuncts of a Python expression like ``"a or b or c"`` reduce to ``"a"``,
``"(not a) and b"``, and ``"(not a and not b) and c"``! We'll talk more about
this later, in the section on `Predicates`_ below.)
 
A disjunction only implies a condition if *all* conditions in the disjunction
imply the other condition::

``(A or B) and (C or D)`` to be transformed into their disjunctive normal
form (i.e. ``(A and C) or (A and D) or (B and C) or (B and D)``).
 
In other words, by using ``Disjunction()`` as an "or" operator and
``intersect()`` as the "and" operator, we always end up with a DNF result.
(In other words, by using ``Disjunction()`` as an "or" operator and
``intersect()`` as the "and" operator, we always end up with a DNF result!)
 
 
Object Identity

``y is x`` condition would be true. Conversely, ``IsObject(x, False)``
represents the set of objects ``y`` for whom ``y is not x``::
 
    >>> from peak.rules.criteria import IsObject, NotObjects
    >>> from peak.rules.criteria import IsObject, Conjunction
 
    >>> o = object()
    >>> is_o = IsObject(o)

    False
 
And the intersection of multiple ``is not`` conditions produces a
``NotObjects`` set::
``Conjunction``::
 
    >>> not_foo = IsObject("foo", False)
    >>> not_bar = IsObject("bar", False)
    >>> not_foobar = intersect(not_foo, not_bar)
    >>> not_foobar
    NotObjects([IsObject('foo', False), IsObject('bar', False)])
    Conjunction([IsObject('foo', False), IsObject('bar', False)])
 
Which of course then implies each of the individual "not" conditions::
 

    >>> implies(not_foobar, IsObject("bar"))
    False
 
Oh, and an ``is`` condition implies any ``NotObjects`` that don't contain its
Oh, and an ``is`` condition implies any ``Conjunction`` that don't contain its
opposite::
 
    >>> implies(is_o, not_foobar)

    >>> implies(not_foobar, is_o)
    False
 
Finally, negating a ``NotObjects`` returns a disjunction of true ``IsObject``
tests, and vice versa::
Finally, negating a ``Conjunction`` of is-nots returns a disjunction of true
``IsObject`` tests, and vice versa::
 
    >>> negate(not_foobar)
    DisjunctionSet([IsObject('foo', True), IsObject('bar', True)])
 
    >>> negate(DisjunctionSet([IsObject('foo'), IsObject('bar')]))
    NotObjects([IsObject('foo', False), IsObject('bar', False)])
    Conjunction([IsObject('foo', False), IsObject('bar', False)])
 
 
Values and Ranges

    >>> intersect(Class(object), Class(int))
    Class(<type 'int'>, True)
 
The intersection of two or more unrelated ``Class`` criteria is a ``Classes``
set::
The intersection of two or more unrelated ``Class`` criteria is represented by
a ``Conjunction``::
 
    >>> from peak.rules.criteria import Classes
    >>> from peak.rules.criteria import Conjunction
 
    >>> intersect(Class(int, False), Class(str, False)) == Classes(
    >>> intersect(Class(int, False), Class(str, False)) == Conjunction(
    ... [Class(int, False), Class(str, False)]
    ... )
    True
 
``Classes`` is a subclass of ``Conjunction``, so all the standard
rules of intersection and implication apply.
 
 
Exact-Type and Type-Exclusion Tests
===================================

    False
 
Unless both are exclusion tests on different types, in which case their
intersection is a ``Classes()`` conjunction::
intersection is a ``Conjunction`` of the two::
 
    >>> intersect(istype(str, False), istype(int, False)) == Classes([
    >>> intersect(istype(str, False), istype(int, False)) == Conjunction([
    ... istype(int, False), istype(str, False)
    ... ])
    True

    istype(<type 'object'>, True)
 
But when it's intersected with a type exclusion test, the result is a
``Classes`` conjunction::
``Conjunction``::
 
    >>> intersect(istype(int, False), Class(str)) == Classes([
    >>> intersect(istype(int, False), Class(str)) == Conjunction([
    ... istype(int, False), Class(str, True)
    ... ])
    True
 
    >>> s = intersect(Class(str), istype(int, False))
    >>> s == Classes([istype(int, False), Class(str, True)])
    >>> s == Conjunction([istype(int, False), Class(str, True)])
    True
 
    >>> intersect(s, istype(int))

 
    >>> negate(
    ... Test('x',
    ... NotObjects([IsObject('foo',False), IsObject('bar',False)])
    ... Conjunction([IsObject('foo',False), IsObject('bar',False)])
    ... )
    ... ) == DisjunctionSet(
    ... [Test('x', IsObject('foo', True)),

criterion is the intersection of the original tests' criteria::
 
    >>> intersect(x_isa_int, Test("x", Class(str))) == Test(
    ... 'x', Classes([Class(int), Class(str)])
    ... 'x', Conjunction([Class(int), Class(str)])
    ... )
    True
 

 
    >>> intersect(x_int_y_str, Test("y", Class(float))) == Signature([
    ... Test('x', Class(int)),
    ... Test('y', Classes([Class(str), Class(float)]))
    ... Test('y', Conjunction([Class(str), Class(float)]))
    ... ])
    True
 
    >>> intersect(x_int_y_str, Test("x", Class(float))) == Signature([
    ... Test('x', Classes([Class(int), Class(float)])),
    ... Test('x', Conjunction([Class(int), Class(float)])),
    ... Test('y', Class(str))
    ... ])
    True
 
    >>> intersect(Test("x", Class(float)), x_int_y_str) == Signature([
    ... Test('x', Classes([Class(int), Class(float)])),
    ... Test('x', Conjunction([Class(int), Class(float)])),
    ... Test('y', Class(str))
    ... ])
    True

 
    >>> DisjunctionSet([OrElse([Class(a), Class(b)])]) == DisjunctionSet([
    ... Class(a, True),
    ... Classes([Class(a, False), Class(b, True)])
    ... Conjunction([Class(a, False), Class(b, True)])
    ... ])
    True
    

 
    >>> set(disjuncts(OrElse([istype(int), a_or_b]))) == set([
    ... istype(int),
    ... Classes([istype(int, False), Class(b)]),
    ... Classes([istype(int, False), Class(a)])
    ... Conjunction([istype(int, False), Class(b)]),
    ... Conjunction([istype(int, False), Class(a)])
    ... ])
    True
 

We'll do one more test, to show that the disjuncts of the negated portions of
the ``OrElse`` are also expanded::
 
    >>> a_and_b = Classes([Class(a), Class(b)])
    >>> a_and_b = Conjunction([Class(a), Class(b)])
    >>> not_a = Class(a, False)
    >>> not_b = Class(b, False)
    >>> int_or_str = DisjunctionSet([Class(int), Class(str)])
 
    >>> set(disjuncts(OrElse([a_and_b, int_or_str]))) == set([
    ... a_and_b, Classes([not_a, Class(int)]), Classes([not_a, Class(str)]),
    ... Classes([not_b, Class(int)]), Classes([not_b, Class(str)])
    ... a_and_b, Conjunction([not_a, Class(int)]), Conjunction([not_a, Class(str)]),
    ... Conjunction([not_b, Class(int)]), Conjunction([not_b, Class(str)])
    ... ])
    True
 
per this expansion logic (using ``|`` for "symmetric or")::
 
    (a and b) or (int|str) => (a and b) | not (a and b) and (int or str)
    (a and b) or (int|str) => (a and b) | not (a and b) and (int|str)
 
    not (a and b) and (int or str) => (not a or not b) and (int or str)
    not (a and b) and (int|str) => (not a | not b) and (int|str)
 
    (not a or not b) and (int or str) => (
    (not a | not b) and (int|str) => (
        (not a and int) | (not a and str) | (not b and int) | (not b and str)
    )
 

PythonPowered
ShowText of this page
EditText of this page
FindPage by browsing, title search , text search or an index
Or try one of these actions: AttachFile, DeletePage, LikePages, LocalSiteMap, SpellCheck