Conditional acceptance

\label{des/ext/accept} The accept statement that we have considered allows only the names of methods for which a call is acceptable. We will now introduce a much more powerful mechanism that allows, among other things, the imposition of arbitrary conditions on the arguments of the call. The format of the conditional accept statement is \dlpindex{\condacceptexpr} The conditional accept statement is similar to the accept statement treated previously except that, instead of a method name m, expressions of the form [] m(t1,...,t_n):guard -> goal and simplifications thereof, as listed below, may occur as arguments.

Semantics

When an accept statement is encountered, for instance when evaluating the constructor of an object, the accept expressions occurring in the statement are stored in the so-called accept list of the object. The accept list is consulted for each request to evaluate a method call. For simple accept statements the accept list consists of method names. Whether a method call is acceptable then only depends on the method name being a member of the accept list. Checking whether a method call satisfies the acceptance condition imposed by an accept expression of the form m(t1,...,t_n):guard -> goal requires more effort. When a method is called, say by m(t1',...,t_n'), then it is first tried whether the call can be unified with the expression m(t1,...,t_n). If the unification is successful then, in addition, the guard will be evaluated, instantiated by the bindings that result from the unification of the call with the method template m(t1,...,t_n). If the evaluation of the guard succeeds also, the call will be answered by evaluating goal, instantiated by the bindings resulting from unifying the call with the template and the evaluation of the guard. It is easy to see that the conditional accept statement subsumes the original accept statement since the statement accept(m) has meaning identical to [] accept(m(t1,...,t_n): true -> m(t1,...,t_n)) Both the guard and the goal of an accept expression may contain variables occurring in the method template m(t1,...,t_n). The bindings computed to answer the caller are determined by the evaluation of the goal and, in addition, by both the unification with the template and the evaluation of the guard. Below we indicate what happens when an accept expression of a simpler form is encountered.

Accept expressions

\dlpindex{accept expressions} The arguments of the accept statement are called accept expressions. Accept expressions may take one of the following forms. In addition we have that executes goal for all calls unifying with m(t1,...,t_n) for which the guard holds. To illustrate the power of the generalized accept statement we re-express some of the examples presented earlier.

Examples

We will first give an alternative declaration for the object sema. \lprog{sema}{ .ds sema1.pl } The behavior of an instance of sema is identical to the behavior of the object sema as defined before. The present declaration differs from the previous one in that the Prolog conditional goal n == 0 -> answer(v) ; answer(p,v) is replaced by the conditional accept statement accept(v:N >= 0, p:N>0) with N bound to n, in which the guards contain the conditions under which the method calls may be accepted.

States

Perhaps somewhat surprisingly, we no longer need to use non-logical variables to maintain the state of an (active) object. We will illustrate this by (re) declaring our familiar counter. \lprog{ctr}{ .ds ctr4.pl } The state is passed as an argument in a tail-recursive call to run, which implements the body of the object. In a similar way we can implement a semaphore, as shown below. \lprog{sema}{ .ds sema2.pl } which is a rather elegant way of coding a semaphore. Notice that we do not have to specify clauses for the methods but may specify the functionality of a method in the goal part of an accept expression. Non-logical variables are no longer necessary to represent the state of an object because of the enlarged functionality of the accept statement. However, logical state variables, maintained as an argument in a tail-recursive loop, may not be inherited whereas non-logical state variables may be inherited among objects, thus allowing a rather concise description of the functionality of a collection of objects. Another reason not to abandon non-logical variables has to do with efficiency. Passing a complex state as an argument after each method call is clearly less efficient.

Backtracking

The generalized accept statement preserves backtracking over the possible answers delivered in a rendez-vous, as illustrated by our rephrasing of the declaration of a travel agency. \lprog{travel}{ .ds travel6.pl } As before we may generate all reachable cities by stating the goal ?- travel!reachable(X). Since the goal in a conditional accept expression may fail, care must be taken to update the state variable in a proper way, as illustrated in the example above where the parameter for the tail-recursive call to run (L1) is bound to the original list L.