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
[]
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
requires more effort.
When a method is called, say by ,
then it is first tried whether the call can be unified with the expression
.
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 .
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 has meaning identical to
[]
Both the guard and the goal of an accept expression
may contain variables occurring in the method template .
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.
- m --- accepts all calls for method m
- --- accepts all calls that unify with
- --- accepts all calls for m if the guard holds
- --- accepts all calls that unify with
for which the guard holds
- --- executes goal for all calls to m if
the guard holds
In addition we have
-
that executes goal for all calls
unifying with 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.