Every time the relevant variable bounds change, the delayed ge/2 goal wakes up and (as long as there are still two variables) a new, identical goal gets delayed. To better support this situation, 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 a demon, 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 have a handle to itself (its ‘suspension’) in one of its arguments. This can then be used to kill the suspension when required:
ge(X, Y) :- suspend(ge(X,Y,MySusp), 0, [X->ic:max, Y->ic:min], MySusp), ge(X, Y, MySusp). :- demon ge/3. ge(X, Y, MySusp) :- get_bounds(X, _, XH), get_bounds(Y, YL, _), ( var(X),var(Y) -> true % implicitly re-suspend ; kill_suspension(MySusp) ), X #>= YL, % impose new bounds Y #=< XH. |
We have used the new primitives suspend/4 and kill_suspension/1.