7.2 Coroutining
Coroutining allows us to delay the execution of Prolog goals until their truth can be safely decided.
Among the most important coroutining predicates is dif/2, which expresses disequality of terms in a sound way. The actual test is delayed until the terms are sufficiently different, or have become identical. For example:
?- dif(X, Y), X = a, Y = b. X = a, Y = b. ?- dif(X, Y), X = a, Y = a. false.
There are also lower-level coroutining predicates that are intended as building blocks for higher-level constraints. For example, we can use freeze/2 to define a variable that can only be assigned an atom:
?- freeze(X, atom(X)), X = a. X = a.
In this case, calling atom/1 earlier causes the whole query to fail:
?- atom(X), X = a. false.
If available, domain-specific constraints should be used in such cases. For example, to state that a variable can only assume even integers, use the CLP(FD) constraint #=/2:
?- X mod 2 #= 0. X mod 2#=0.
Importantly, domain-specific constraints can apply stronger propagation by exploiting logical properties of their respective domains. For example:
?- X mod 2 #= 0, X in 1..3. X = 2.
Remaining constraints, such as X mod 2#=0
in the example
above, are called residual goals.
They are said to
flounder, because their truth is
not yet decided. Declaratively, the query is only true if all residual
goals are satisfiable. Use call_residue_vars/2
to collect all variables that are involved in constraints.
- dif(@A, @B)
- The dif/2
predicate is a constraint that is true if and only if A
and B are different terms. If A and B
can never unify, dif/2
succeeds deterministically. If A and B are
identical, it fails immediately. Finally, if A and B
can unify, goals are delayed that prevent A and B
to become equal. It is this last property that makes dif/2
a more general and more declarative alternative for \=/2
and related predicates.
This predicate behaves as if defined by
dif(X, Y) :- when(?=(X,Y), X \== Y)
. See also ?=/2. The implementation can deal with cyclic terms.The dif/2 predicate is realised using attributed variables associated with the module
dif
. It is an autoloaded predicate that is defined in the librarylibrary(dif)
. - freeze(+Var, :Goal)
- Delay the execution of Goal until Var is bound
(i.e. is not a variable or attributed variable). If Var is
bound on entry
freeze/2
is equivalent to call/1.
The freeze/2
predicate is realised using an attributed variable associated with the
module
freeze
. Usefrozen(Var, Goal)
to find out whether and which goals are delayed on Var. - frozen(@Var, -Goal)
- Unify Goal with the goal or conjunction of goals delayed on
Var. If no goals are frozen on Var, Goal
is unified to
true
. - when(@Condition, :Goal)
- Execute Goal when Condition becomes true. Condition
is one of
?=(X, Y)
,nonvar(X)
,ground(X)
,
or,
(Cond1, Cond2)
. See also freeze/2 and dif/2. The implementation can deal with cyclic terms in X and Y.;
(Cond1, Cond2)The when/2 predicate is realised using attributed variables associated with the module
when
. It is defined in the autoload librarylibrary(when)
. - call_residue_vars(:Goal, -Vars)
- Find residual attributed variables left by Goal. This predicate is intended for reasoning about and debugging programs that use coroutining or constraints. To see why this predicate is necessary, consider a predicate that poses contradicting constraints on a variable, and where that variable does not appear in any argument of the predicate and hence does not yield any residual goals on the toplevel when the predicate is invoked. Such programs should fail, but sometimes succeed because the constraint solver is too weak to detect the contradiction. Ideally, delayed goals and constraints are all executed at the end of the computation. The meta predicate call_residue_vars/2 finds variables that are given attributes or whose attributes are modified by Goal, regardless of whether or not these variables are reachable from the arguments of Goal.154The implementation of call_residue_vars/2 is completely redone in version 7.3.2 (7.2.1) after discussion with Bart Demoen. The current implementation no longer performs full scans of the stacks. The overhead is proportional to the number of attributed variables on the stack, dead or alive..