Call-out Call-in Solution Limiter

We finally give an example that will show the full power of the Prolog API. We will define a Java foreign predicate that will redoably call a given goal and terminate it when the solution count crosses a given threshold. We therefore need a choice point with a state. We use a simple primitive integer field in the example class:

   public class OutInLimit {
private final CallIn callin;
private int count = 0;
private OutInLimit(CallIn c) {
callin = c;
}

We will use the same method name. The method will have a term argument and an integer argument besides the interpreter and call-out argument. The call-out argument is needed because the predicate will be non-deterministic. The interpreter exception is mentioned to be able to pass down any exception thrown by the goal argument.

     public static boolean limit(Interpreter inter,
CallOut callout,
AbstractTerm goal, int max)
throws InterpreterException, InterpreterMessage {

The implementation of the foreign function limit() starts with obtaining the choice point data. If the call is the first call, then the choice point data is created and set to the call-out parameter. The choice point data will receive a new iterator for the given goal. If the call is not the first call, the choice point data is obtained from the call-out parameter:

        OutInLimit limit;
if (callout.getFirst()) {
limit = new OutInLimit(inter.iterator(goal));
callout.setData(limit);
} else {
limit = (OutInLimit) callout.getData();
}

We need also to care for a clean-up call. This happens when in the continuation a cut or an exception happens. The code here is:

        if (callout.getCleanup()) {
            InterpreterException ex = callout.getException();
            ex = limit.callin.cleanup(ex);
            callout.setException(ex);
            return true;
        }

As a first step we can check whether we have already reached the iteration limit. If the itera-tion limit is already reached we close the iterator and fail the foreign function:

        if (!(limit.count < max)) {
limit.callin.close();
return false;
}

Otherwise, if we have not yet reached the limited we check whether the given goal produces more results. If this is the case we can increase the counter and notify the interpreter that we are expecting redo calls by invoking the method setRetry(). Otherwise we fail our foreign predicate by returning false:

        if (limit.callin.hasNext()) {
limit.callin.next();
limit.count++;
callout.setRetry(true);
callout.setSpecial(true);
callout.setCutter(true);
return true;
}
return false;

The above code informs the interpreter that we do not want to rely on the default barrier handling. This is done by invoking the method setSpecial() before successfully returning. This is necessary since the default barrier handling would undo all variable bindings upon redo and thus corrupting the prior success of the goal. Further the above code also informs the interpreter we handle clean up events. This is done by invoking the method setCutter() before successfully returning.

The Java method for the foreign predicate will be part of the Java class OutInLimit which has already been used for the choice point data. As an example we will limit the employee/1 predicate from one of the previous examples. Therefore we will reuse the employee foreign predicate from the Java class OutTable. We first proceed in registering the Java method in the main method of the Java class OutInLimit. We only show the Java method registrations for our new foreign predicate limit/2:

        foreignGoal = inter.parseTerm("foreign(limit/2, " +
"'OutInLimit', limit('Interpreter', 'CallOut', " +
"'AbstractTerm', 'int'))");
inter.iterator(foreignGoal).next().close();
The main method will proceed by first creating an employee goal and then a limit goal. The employee goal will need a variable, but the limit goal will not need a variable. We will use the values max=3, max=4 and max=6.  For the first scenario with max=3 the main method will then use a loop to enumerate all solutions to the limit goal. The following lines will be displayed in the first scenario on the standard output:
  bundy
  canby
  ozias

We can differently proceed for the second scenario with max=4. In this scenario we will not loop through all results but use nextClose() and thus put the cutter to a test. The following lines will be displayed in the second scenario on the standard output:

  bundy

The last scenario with max=6 will correspond again to the first scenario, in that we will loop again. The following lines will be displayed in the last scenario on the standard output:

  bundy
  canby
  ozias
  watson

Alternatively we can use the Prolog runtime to invoke queries. The corresponding Prolog text is given in the appendix as ‘limitoutin.p’ in the folder 'example05'.

Kommentare