next up previous index
Next: The Top-Level Loop Up: Advanced Control Features Previous: Printing Suspensions   Index

Subsections


The Standard suspend Attribute

ECLiPSe defines a standard attribute called suspend which provides a general coroutining mechanism which serves as a basis for various other extensions that use goal suspension and waking. It defines an attribute and several suspension lists which are woken on very general conditions and which can be accessed by other extensions and also some general utility predicates.

The attribute has the name suspend and it is represented by the structure

suspend with [inst:I, constrained:C, bound:B]

The lists have the following meaning:

The inst and bound lists are woken implicitly by the unification routine. New extensions which want to be notified about these events are supposed to use this library instead of defining their own lists that should be woken on instantiation and binding.

The constrained list must be explicitly woken by every extension which imposes more constraints on the variable. For example, in case of the fd library this means that the domain of the variable was reduced, in the r library this is the case when a new equation containing this variable was inserted. As the system cannot itself decide whether the variable was constrained or not, it is the sole responsibility of each extension to notify the system of the constraining. This is done by calling the predicate

notify_constrained(Var)
whenever the variable Var becomes more constrained. As the constrained list is automatically woken when the variable is instantiated or bound, this predicate should be called only when the variable is constrained in an extension-specific way.

The suspend attribute is defined with handlers for the meta-term events

As no printing handler is defined, the suspended lists are normally not visible.


The suspend/3,4 Predicates

The predicates suspend/3 and suspend/4 provide a simplified interface to suspend goals and wake them on specified conditions, which is easier to use than the low-level predicates make_suspension/3 and insert_suspension/3. When
suspend(Goal, Prio, CondList)
is called, Goal will be suspended with priority Prio and it will wake up as soon as one of the condition specified in the CondList is satisfied. This list contains specifications of the form
Vars -> Cond
to denote that as soon as one of the variables in the term Vars will satisfy the condition Cond, the suspended goal will be woken and then executed as soon as the program priority allows it. CondList can also be a single specification.

The condition Cond can be the name of a system-defined suspension list, e.g.

(X,Y) -> inst
means that as soon as one (or both) of the variables X, Y will be instantiated, the suspended goal will be woken. These variables are also called the suspending variables of the goal.

Cond can also be the specification of a suspension list defined in one of currently available attributes. E.g. when the finite domain library is loaded

f(A, B) -> fd:min
triggers the suspended goal as soon as the minimum element of the domains of A or B are updated (see Constraint Library Manual, Finite Domain Library).

Another admissible form of condition Cond is

trigger(Name)
which suspends the goal on the global trigger condition Name (see section 17.4.2).


Particularities of Waking by Unification

Goals that are suspended on the inst and bound lists of the suspend attribute are woken by unifications of their suspending variables. One suspending variable can be responsible for delaying several goals, on the other hand one goal can be suspended due to several suspending variables. This means that when one suspending variable is bound, several delayed goals may be woken. The order of waking suspended goals does not necessarily correspond to the order of their suspending. It is in fact determined by their priorities and is implementation-dependent within the same priority group.

The waking process never interrupts the unification and/or a sequence of simple goals. Simple goals are a subset of the built-ins and can be recognised by their call_type flag as returned by get_flag/3, simple goals having the type external. Note also that some predicates, e.g. is/2, are normally in-line expanded and thus simple, but would be regular under pragma(noexpand) directive.

ECLiPSe treats simple predicates (including unification) always as a block. Delayed goals are therefore woken only at the end of a successful unification and/or a sequence of simple goals. If a suspending variable is bound in a simple goal, the suspended goals are woken only at the end of the last consecutive simple goal or at the clause end. If the clause contains some simple goals at the beginning of its body, they are considered part of the head (extended head, or prefix) and if a suspending variable is bound in the head unification or in a simple predicate in the extended head, the corresponding delayed goals are woken at the end of the extended head.

A cut is always executed before waking any pending suspended goals. This is important especially in the situations where the cut acts like a guard, immediately after the clause neck or after a sequence of simple goals. If the goals woken by the head unification or by the extended head are considered as constraints on the suspending variables, the procedure will not behave as expected. For example

delay filter(_, Li, Lo) if var(Li), var(Lo).
filter(P,[],[]) :- !.
filter(P,[N|LI],[N|NLI]) :-
        N mod P =\= 0,
        !,
        filter(P,LI,NLI).
filter(P,[N|LI],NLI) :-
        N mod P =:= 0,
        filter(P,LI,NLI).

delay integers(_, List) if var(List).
integers(_, []).
integers(N, [N|Rest]) :-
        N1 is N + 1,
        integers(N1, Rest).
 
:- integers(2, L), filter(2, L, [N|R])
Here the call to integers/2 delays, when filter/3 is called, L is instantiated in the head unification, but integers/2 will not be woken until the cut is executed, instead of waking it immediately and proceed to the third clause of filter/3 after the woken goal fails. Therefore the call to =\= also delays and the cut cuts the third clause. Only then the call to integers/2 is woken, it fails and the whole goal fails instead of executing the third clause as expected. This is yet another example why cut should not be used together with coroutining. Note also that the ECLiPSe debugger will emit a warning due to the improper use of the cut. The reason why delayed goals are woken after the cut and not before it is that neither of the two possibilities is always the intended or the correct one, however when goals are woken before the cut, there is no way to escape it and wake them after, and so if a nondeterministic goal is woken, it is committed by this cut which was most probably not intended. On the other hand, it is always possible to force waking before the cut by inserting a regular goal before it, for example true/0, so the sequence
true, !
can be viewed as a special cut type. The example above should be correctly written using the if-then-else construct, which always forces waking suspended goals before executing the condition. This would also save trailing the second argument and creating a choice point:
filter(P,[],[]) :- !.
filter(P,[N|LI],LL) :-
        (N mod P =\= 0 ->
                LL = [N|NLI],
                filter(P, LI, NLI)
        ;
                filter(P,LI,LL)
        ).


next up previous index
Next: The Top-Level Loop Up: Advanced Control Features Previous: Printing Suspensions   Index
Warwick Harvey
2004-08-07