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
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 -> Condto 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) -> instmeans 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:mintriggers 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).
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
filter(P,[],[]) :- !. filter(P,[N|LI],LL) :- (N mod P =\= 0 -> LL = [N|NLI], filter(P, LI, NLI) ; filter(P,LI,LL) ).