PREDICATE

A predicate p over a type is a total function which maps elements a of the type to the boolean value p(a). The result true indicates that a satisfies the predicate, false indicates that a does not satisfy the predicate.

All elements which satisfy the predicate form a set. Therefore we can identify each predicate with the set of all elements which satisfy the predicate.

The Predicate Type

The class PREDICATE is generic and needs a type argument to give a predicate type. E.g. the type PREDICATE[NATURAL] is a predicate over natural numbers.

The module predicate in its interface file predicate.ali starts with the following declarations

use
    any     -- module 'boolean' used implicitely
end

A: ANY

class
    PREDICATE[A]
inherit
    ghost ANY
end

The type argument for the predicate has to be a descendant of ANY. Types which do not inherit ANY cannot be used to form predicates. This is one of the reasons why all useful classes must inherit ANY i.e. they must have a definition of equality.

The declaration of PREDICATE contains an inherit clause which states that PREDICATE itself inherits ANY. This is the reason why we can form predicates over predicates. E.g. the type PREDICATE[PREDICATE[NATURAL]] describes a collection of sets of natural numbers.

The inheritance relation is marked as a ghost inheritance. This is necessary because the equality of predicates is not computable (see below).

Prediates are very frequently used in Albatross. Therefore there is a shorthand. The type {NATURAL} is equivalent to PREDICATE[NATURAL] and the type {{NATURAL}} is equivalent to PREDICATE[PREDICATE[NATURAL]].

Basic Predicate Functions

Now we come to some basic functions of PREDICATE.

(in) (a:A, p:{A}): BOOLEAN
        -- Is 'a' an element of the set 'p'?

(/in) (a:A, p:{A}): BOOLEAN
        -- Is 'a' not an element of the set 'p'?
    -> not (a in p)

(<=) (p,q:{A}): ghost BOOLEAN
        -- Is `p` a subset of `q`?
    -> all(x) x in p ==> x in q

(=)  (p,q:{A}): ghost BOOLEAN
        -- Are `p` and `q` equal sets?
    -> p <= q  and  q <= p

The operator in is given without any definition. The expression x in p is equivalent to p(x). The former emphasizes the fact that predicates represent sets (i.e. the set of all elements which satisfy the predicate), the latter emphasizes the fact that predicates are functions. It is usually better to use x in p instead of p(x) because it is more intuitive.

The less-equal operator <= is used to define the subset function. A set (aka predicate) p is a subset of the set q if all elements of p are contained in the set q. This is exactly what the definition of the operator <= says. Since the definition contains a quantified expression, the function has to be marked as a ghost function (see chapter Functions for details).

The equality operator = says that two sets are equal if they are mutually subsets. This function does not use quantified expressions in its definition term but it uses the ghost function <=. Therefore it has to be marked as ghost function as well.

The equality operator is definitely reflexive. Therefore PREDICATE is entitled to inherit ANY. However since the equality function is a ghost function the inheritance relation has to be marked as a ghost inheritance.

Predicate Expressions

We have already seen expressions like x in p, x /in p, p <= q. However all these expressions contain variables which are predicates.

There remains the question: How can predicates be created?. Answer: With predicate expressions.

Since a predicate is a boolean valued function we can define a predicate by defining a variable and use an expression which computes whether the variable satisfies the expression. The general form of a predicate expression is

{variable(s): boolean_expression}

The variables must be annotated with types only if the types cannot be inferred. Usually the compiler can infer the type of the variable(s).

Since predicates are total functions the boolean expression must be well defined for all variables. If an expression is not well defined for all variables (i.e. it has preconditions) then appropriate guards have to be put in front of the expression with preconditions.

Assume that e2 has preconditions and e1 evaluates to true only if the preconditions are satisfied then the following two predicate expressions are valid while a predicate expression without the guards would not be valid.

{variable(s): e1 ==> e2}
{variable(s): e1 and e2}

Since boolean expressions are lazily evaluated the expression e2 is evaluated only if e1 evaluates to true.

The boolean expression within a predicate expression might or might not contain the variable(s). Since there are only two boolean constants we can define the empty and the universal set with a predicate expression.

{x:NATURAL: false}        -- empty set of natural numbers

{x:NATURAL: true}         -- universal set of natural numbers

The first expression is a function which maps each natural number to false which says that no natural number can satisfy this predicate. The second expression maps all numbers to true, therefore all numbers satisfy this predicate.

These expressions are used to define the empty and the universal set in a generic way.

empty: {A}
        -- The empty set.
    = {x: false}

universal: {A}
        -- The universal set.
    = {x: true}

empty and universal are two generic constants of the module predicate which return predicates to represent the empty and the universal set.

Note that the type annotation in the predicate expressions is no longer necessary because the compiler infers the type of x from the fact that the constants are declared with type {A}.

Beta Reduction

For those familiar with lambda calculus it is not hard to see that predicate expressions are lambda expressions. In lambda calculus the expression {x: true} would be expressed as lambda x. true.

In lambda calculus expressions can be beta reduced. E.g.

(lambda x. p) a    ~>  p[x:=a]

where p[x:=a] means that all occurrences of the variable x in the expression p are substituted by a.

The Albatross compiler can use beta reduction to symbolically evaluate an expression of the form a in p if p is given as a predicate expression.

E.g.

a in {x: exp}

evaluates to

exp[x:=a]            -- this is not Albatross syntax

The compiler can do this evaluation symbolically because beta reduction requires only the substitution of a variable by an expression.

Leibniz Equality

We have already seen that equality is defined in the module any and any type inheriting ANY must define an equality operator which is reflexive in order to be entitled to inherit ANY.

However this is not the whole story. In Albatross equality has much stronger properties which are enforced by the compiler. In Albatross two expressions can be equal only if they cannot be distinguished. I.e. if a = b is valid then all predicates which are satisfied by a must be satisfied by b as well. Otherwise the expressions would be distinguishable by some predicate.

This type of equality is generally called Leibniz equality. Having Leibniz equality guarantees that equals can always be substituted by equals.

Since we need predicates to define Leibniz equality the corresponding law is included in the module predicate. It reads

all(a,b:A, p:{A})
    ensure
        a = b ==> a in p ==> b in p
    end

Remember that Leibniz equality is enforced by the compiler. Relying on Leibniz equality the module predicate can prove in its implementation file that equality is symmetric and transitive. The interface file of the module predicate just states these facts as proved.

all(a,b,c:A)
    ensure
        a = b  ==>  b = a               -- symmetric

        a = b  ==>  b = c  ==>  a = c   -- transitive
    end
Usage of Predicates to Define Set Algebra

The module predicate of the library alba.base enriches the basic declarations of the module core to more interesting things with predicates like set algebra.

Predicate expressions can be used to express more interesting facts. It is easy to define set intersection and set union using predicate expressions.

(*) (p,q: {A}): {A}
        -- The intersection of the set 'p' with the set 'q'
    -> {x: x in p  and  x in q}

(+) (p,q: {A}): {A}
        -- The union of the set 'p' with the set 'q'
    -> {x: x in p  or  x in q}

These functions are not ghost functions. They are perfectly computable.

The same applies to set complement and set difference.

(-) (p:{A}): {A}
        -- The complement of the set 'p'
    -> x /in p

(-)  (p,q:{A}): {A}
        -- The set 'p' without the elements of the set 'q'
    -> {x: x in p and x /in q}

The operator - can be used to define set complement and set difference. This is possible because in Albatross only the function name together with its signature has to be unique. Since the signatures of these two functions are different the functions are different.

All the set algebra functions (union, intersection, complement, etc.) are defined in the module predicate_logic. The module predicate just defines the most basic things about predicates.

Collection of Sets

Further more the module predicate exposes definitions to work with collection of sets. This is possible because PREDICATE inherits ANY.

The type {{A}} represents a collections of sets of elements of type A. We can also form a collection of collections of sets like {{{A}}}.

If we have a collection of sets i.e. a predicate of type {{A}} we can form the union and the intersection of this collection of sets. The union of a collection of sets is a set which contains all elements which are contained in at least one set of the collection of sets. The intersection of a collection of sets is a set which contains elements which are contained in all sets of the collection.

It is easy to define the union and intersection of collection of sets in Albatross.

(+) (ps:{{A}}): ghost {A}
        -- The union of the collection of sets 'ps'.
    -> {x: some(p) p in ps  and  x in p}

(*) (ps:{{A}}): ghost {A}
        -- The intersection of the collection of sets 'ps'.
    -> {x: all(p) p in ps  ==>  x in p}

Evidently these functions have to be marked as ghost functions since their definition terms are not computable. Note that + and * can be used as binary and as unary operators. Since the signatures of these functions are different from all other functions using these operators there is no ambiguity.

Enumerated Sets

In mathematics it is possible to define a set by enumerating its elements. E.g. {a,b,c} is the set which contains the elements a, b and c.

In Albatross this is possible as well. However it is important to understand what the expression {a,b,c} means in Albatross.

The module predicate defines the function

singleton(a:A): {A}
        -- The singleton set with the only element `a`.
    -> {x: x = a}

The Albatross parser has some macro mechanism which transforms expressions of the form {a,b,...} into a.singleton + b.singleton + ....

I.e. the expression {a} is a shorthand for a.singleton and the expression {a,b,c,...} is a shorthand for {a} + {b} + {c} + ....

Therefore the expression

z in {a,b}

evaluates to

z in {a} + {b}            -- arithmetic operator '+' binds stronger than
                          -- the relational operator 'in'

z in a.singleton + b.singleton

z in {x: x in a.singleton or x in b.singleton}

z in a.singleton or z in b.singleton

z = a  or  x = b

which is inline with our intuition about enumerated sets.

Relations with Predicates

The module relation of the library alba.base uses predicates to define binary relations.

Since tuples (see chapter Tuple) inherit ANY it is possible to form predicates over tuples.

Assume that there are the two type variables A and B which can be substituted by any type inheriting ANY. Then the type {A,B} is a predicate over tuples i.e. it represents a set of pairs which is a binary relation.

Note that such a definition has to use the module tuple to be valid and the module tuple uses the module predicate. Therefore the module predicate cannot define functions using relations without creating a circular module dependency.

Therefore the base library has a module relation which uses tuple and predicate and can therefore define functions using relations. E.g. the module relation defines the following functions

A: ANY
B: ANY

domain(r:{A,B}): ghost {A}
        -- The domain of the relation 'r'.
    -> {a: some(b) r(a,b)}

range (r:{A,B}): ghost {B}
        -- The range of the relation 'r'.
    -> {b: some(a) r(a,b)}

inverse(r:{A,B}): {B,A}
        -- The inverse of the relation 'r'.
    -> {b,a: r(a,b)}

It is not difficult to see that these definitions corresponds to the usual definitions in mathematics. E.g. the domain is the set of all elements a such that there is a corresponding element b and the pair (a,b) figures in the relation.

As opposed to sets where it is usually more intuitive to write a in p instead of p(a) with relations it is usually more intuitive to write r(a,b) instead of (a,b) in r. But both versions are equally valid in Albatross.

results matching ""

    No results matching ""