35.3 Introductory Examples

We define a CHR constraint for less-than-or-equal, leq, that can handle variable arguments. This handler can be found in the library as the file leq.pl. (The code works regardless of options switched on or off.)

     :- use_module(library(chr)).
     
     handler leq.
     constraints leq/2.
     :- op(500, xfx, leq).
     
     reflexivity  @ X leq Y <=> X=Y | true.
     antisymmetry @ X leq Y , Y leq X <=> X=Y.
     idempotence  @ X leq Y \ X leq Y <=> true.
     transitivity @ X leq Y , Y leq Z ==> X leq Z.

The CHR specify how leq simplifies and propagates as a constraint. They implement reflexivity, idempotence, antisymmetry and transitivity in a straightforward way. CHR reflexivity states that X leq Y simplifies to true, provided it is the case that X=Y. This test forms the (optional) guard of a rule, a precondition on the applicability of the rule. Hence, whenever we see a constraint of the form A leq A we can simplify it to true.

The rule antisymmetry means that if we find X leq Y as well as Y leq X in the constraint store, we can replace it by the logically equivalent X=Y. Note the different use of X=Y in the two rules: In the reflexivity rule the equality is a precondition (test) on the rule, while in the antisymmetry rule it is enforced when the rule fires. (The reflexivity rule could also have been written as reflexivity X leq X <=> true.)

The rules reflexivity and antisymmetry are simplification CHR. In such rules, the constraints found are removed when the rule applies and fires. The rule idempotence is a simpagation CHR, only the constraints right of '\' will be removed. The rule says that if we find X leq Y and another X leq Y in the constraint store, we can remove one.

Finally, the rule transitivity states that the conjunction X leq Y, Y leq Z implies X leq Z. Operationally, we add X leq Z as (redundant) constraint, without removing the constraints X leq Y, Y leq Z. This kind of CHR is called propagation CHR.

Propagation CHR are useful, as the query A leq B,C leq A,B leq C illustrates: The first two constraints cause CHR transitivity to fire and add C leq B to the query. This new constraint together with B leq C matches the head of CHR antisymmetry, X leq Y, Y leq X. So the two constraints are replaced by B=C. Since B=C makes B and C equivalent, CHR antisymmetry applies to the constraints A leq B, C leq A, resulting in A=B. The query contains no more CHR constraints, the simplification stops. The constraint handler we built has solved A leq B, C leq A, B leq C and produced the answer A=B, B=C:

     A leq B,C leq A,B leq C.
     % C leq A, A leq B propagates C leq B by transitivity.
     % C leq B, B leq C simplifies to B=C by antisymmetry.
     % A leq B, C leq A simplifies to A=B by antisymmetry since B=C.
     A=B,B=C.

Note that multiple heads of rules are essential in solving these constraints. Also note that this handler implements a (partial) order constraint over any constraint domain, this generality is only possible with CHR.

As another example, we can implement the sieve of Eratosthenes to compute primes simply as (for variations see the handler primes.pl):

     :- use_module(library(chr)).
     handler eratosthenes.
     constraints primes/1,prime/1.
     
     primes(1) <=> true.
     primes(N) <=> N>1 | M is N-1,prime(N),primes(M). % generate candidates
     
     absorb(J) @ prime(I) \ prime(J) <=> J mod I =:= 0 | true.

The constraint primes(N) generates candidates for prime numbers, prime(M), where M is between 1 and N. The candidates react with each other such that each number absorbs multiples of itself. In the end, only prime numbers remain.

Looking at the two rules defining primes/1, note that head matching is used in CHR, so the first rule will only apply to primes(1). The test N>1 is a guard (precondition) on the second rule. A call with a free variable, like primes(X), will delay (suspend). The third, multi-headed rule absorb(J) reads as follows: If there is a constraint prime(I) and some other constraint prime(J) such that J mod I =:= 0 holds, i.e. J is a multiple of I, then keep prime(I) but remove prime(J) and execute the body of the rule, true.