Previous Up Next

18.9  Demon Predicates

A common pattern when implementing data-driven algorithms is the following variant of the report/1 example from above:

report(X) :-
      suspend(report1(X), 1, X->constrained).      % suspend

report1(X) :-
        ( var(X) ->
            writeln(constrained(X)),
            suspend(report(X), 1, X->constrained)  % re-suspend
        ;
            writeln(instantiated(X))               % die
        ).

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.

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 demon/1 declaration. 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
report(X) :-
      suspend(report(X, Susp), 1, X->constrained, Susp).

:- demon(report/2).
report(X, Susp) :-
      ( var(X) ->
          writeln(constrained(X))   % implicitly re-suspend
      ;
          writeln(instantiated(X)),
          kill_suspension(Susp)     % remove from the resolvent
      ).

Previous Up Next