17.9 Demon Predicates
A common pattern when implementing data-driven algorithms is the following
variant of the report/1 example from above:
Here we have a goal that keeps monitoring changes to its variables.
To do so, it suspends on some or all of those variables.
When a change occurs, it gets woken, does something, and re-suspends.
The repeated re-suspending has two disadvantages: it can be inefficient,
and the goal does not have a unique identifying suspension that could be
easily referred to, because on every re-suspend a new suspension is created.
suspend(report1(X), 1, X->constrained). % suspend
( var(X) ->
suspend(report(X), 1, X->constrained) % re-suspend
writeln(instantiated(X)) % die
To better support this type of goals, ECLiPSe provides a special type
of predicate, called a demon. A predicate is turned into a
demon by annotating it with a
A demon goal differs from a normal goal only in its behaviour on
waking. While a normal goal disappears from the resolvent when it is
woken, the demon remains in the resolvent.
Declaratively, this corresponds to an implicit recursive call in
the body of each demon clause.
Or, in other words, the demon goal forks into one goal that remains in the
suspended part of the resolvent, and an identical one
that gets scheduled for execution.
With this functionality, our above example can be done more
efficiently. One complication arises, however. Since the goal
implicitly re-suspends, it now has to be explicitly killed when
it is no longer needed. The easiest way to achieve this is to
let it remember its own suspension in one of its arguments.
This can then be used to kill the suspension when required:
% A demon that wakes whenever X becomes more constrained
suspend(report(X, Susp), 1, X->constrained, Susp).
report(X, Susp) :-
( var(X) ->
writeln(constrained(X)) % implicitly re-suspend
kill_suspension(Susp) % remove from the resolvent