Interaction Protocol

The duties of a foreign predicate are minimized in that the interpreter automatically performs some housekeeping. The housekeeping includes rolling back the variable creations and instantiations made by the foreign predicate. The housekeeping varies on whether the foreign predicate is deterministic or not. It is based on two primitive operations that allow manipulating the variable bindings and variable creations performed by a predicate. Variable bindings are typically established during unification whereas variable creations are needed for temporary place holders.

The binding and creation of variables can be viewed as filling a log. It is possible to put a mark on the log via the method markFrame(). The log is filled by methods such as unifyTerm(), createVars(), etc... Via the method releaseFrame() the log can be reset to the given mark. As a result all variable bindings and variable creations from the top back to the mark are rolled back. The log does not only support a single mark. It is possible to create multiple marks and if needed one can rollback over multiple marks.

Picture 10: Log Primitives

The log is only a simplified view of what is provided by the system. For example the body variable optimization will remove unused variable bindings and variable creations between the last mark and the top. It will thus reduce the size of the log without following the two primitive operations markFrame() and releaseFrame(). Operations for the body variable optimization are currently not exposed in the Jekejeke Prolog API.

The system now keeps one important mark always on the current. It is the tail recursion barrier. This mark indicates the variable bindings and variable creations up to the last choice point. Characteristic of a deterministic predicate is that it will not create a new choice point and it will therefore never move the tail recursion barrier. A deterministic predicate will either fail or succeeds, but it will never be retried because of its lack of a choice point. The system provides some automatic housekeeping for deterministic predicates.

When a deterministic predicate succeeds the system will automatically call the next goal in the continuation goal list. When a deterministic predicate fails the system will automatically retry the last choice point. When a choice point is tried it will use the tail recursion barrier to roll back the log, provided it is interested in variable bindings or the variable count.

Picture 11: Housekeeping for Deterministic Predicates

Both failed and successful deterministic predicates might fill the log. That a failed predicates might fill the log is a feature that saves some operations. The failed predicate might have allocated some place holders and attempted unification. The failed predicate does not need to establish its own mark and perform a rollback on its own. It can simple delegate this task to the housekeeping of the system.

The tail recursion barrier is also involved in non-deterministic predicates that create choice points. In this respect there is no difference between the first call of a deterministic or non-deterministic predicate. Both deterministic and non-deterministic predicates are called with a tail recursion barrier in place. The predicate can then do some work and call the method markFrame(). The predicate might also then do some further work. If the work did not lead to some result it might call releaseFrame().

If the predicate is satisfied with the result and if it desires a choice point it might then turn the mark into a new barrier. For this purpose it has to call the method swapBarrier(). The predicate will then indicate that it wants a choice point via the method setRetry(). The predicate will also have called setData() so as to tell the system the desired barrier mark. The housekeeping of the system is such that it will create the choice point and call the next goal in the continuation goal list.

Back tracking is usually caused by a deterministic or non-deterministic predicate that fails after a sequence of deterministic predicates that succeeded. The system will first do the normal book keeping for a failed predicate. This consists in rolling back the log to the now new barrier and retrying the choice point. The choice point for the predicate will then invoke the predicate with getFirst() returning false. The return of false by this method informs the predicate that the invocation is not a call but a redo.

It is also possible for the predicate to attach some additional information besides a barrier mark to the choice point. The sole possibility to do this is also the method setData(). The easiest approach is to define a class for the desired data structure and add enough instance fields to the class. Upon redo the additional information and the barrier mark can then be retrieved via the method getData(). Upon redo a predicate has first to undo its move of the barrier via the method swapBarrier(). The predicate can then also call again releaseBind() before doing some further work.

Picture 12: Housekeeping for Non-Deterministic Predicates

Since the barrier handling is very tedious the interpreter does default barrier handling for non-deterministic predicates. Since intervention is impossible the default handling can be switched off by the method setSpecial(). There can be situations where no barrier handling at all is necessary. This is for example the case for certain patterns of doing a call-in by the predicate since the call-in API does already barrier handling. Other situations might require more custom control.

There is also a housekeeping defined for local cuts, normal cuts and execution errors. All three events have in common that they do not simply restore a particular mark. Local cuts rollback all choice points inside the current rule. Normal cuts rollback all choice points skipping cut transparent rules. Execution errors rollback all choice points in the Prolog processor invocation. Currently to be able to handle execution errors new Prolog processor invocations are created, inside which the execution error can happen and then returning to the previous Prolog processor invocation to perform the execution error handling.