Communication by rendez-vous
\label{des/ext/rendez}
In the previous section we have seen how we may implement
objects with states without the use of non-logical variables
to store the state of such objects.
The approach sketched there
has a number of limitations.
Instead of augmenting the proposal of the previous section,
we will take up the main thread of this chapter
and will investigate how we may achieve
object oriented behavior by regarding clauses as methods.
Below we summarize the language features that we treat in this
section.
\dlpindex{\assignexpr}
\dlpindex{\newobjectexpr}
\dlpindex{\methodcallexpr}
\dlpindex{\acceptexpr}
- --- to assign the value of a term to the non-logical variable x
- --- to create an active instance of c, to which O will refer
- --- to call the method m of the object to which O refers
- --- to state the willingness to accept calls for
\nop{
Note that we restrict ourselves to active objects.
}
States
\dlpindex{states}
As we have indicated in the section introducing objects we may
use non-logical variables to store persistent data.
We rephrase the declaration of the counter presented
in the previous section to recall its use.
\lprog{ctr}{
.ds ctr2.pl
}
This solution differs from the previous one in that the state of the object
is not maintained by keeping the value of the counter as an
argument in a tail-recursive loop, but as an explicit
non-logical variable that can be updated by assignment.\ftn{
We allow for arithmetic simplifications both in the right-hand side
of assignments to non-logical variables and in
the left- and right-hand sides of an equality.
}
A typical use of such a counter is exemplified by the goal
.ds ctr2.g
In this a counter is created, which subsequently receives
the method calls and .
Despite the notational similarity with communication over channels,
the calls and
are now method calls, that is goals that are evaluated by the object.
The evaluation of these goals is taken care of by the clauses
defined for inc and value.
Mutual exclusion
\dlpindex{mutual exclusion}
Method calls for an active object must be explicitly
accepted by an accept statement.
To answer a method call an active object must interrupt its own activity.
To protect the access to non-logical variables, mutual exclusion
between method calls is provided by not allowing any method call
to be accepted as long as the evaluation of a method call has not led
to an answer being returned.
An important question to answer is:
when do we allow an object to continue with its own activity?
In the absence of backtracking the natural choice is: immediately after
the answer has been delivered.
In the presence of backtracking we might wish to deliver all answers before allowing
an object to continue its own activity.
However this is overly restrictive
since protection of non-logical variables is not really needed
when backtracking over alternative solutions as shown in section \ref{des/ext/back}.
Another thorny issue, which arises in the presence of backtracking,
is what to do with non-logical variables
that may be assigned values in an imperative way.
Must these assignments be undone on backtracking or not?
As the example of a counter shows, assignment to non-logical
variables is of an imperative nature.
Consequently, such assignments are not undone on backtracking!
\nop{
An instance of a counter is an active object.
Method calls for an active object must be explicitly accepted by
an accept statement.
To protect the access to non-logical variables,
mutual exclusion between method calls is provided
for active objects by not allowing any method call to
be accepted as long as the evaluation of a method call
has not led to an answer being returned.
Having delivered an answer, other method calls
may become active.
As we will see, for passive objects, we will not wish to provide such
protection.
}
Suspension
\dlpindex{suspension}
The mutual exclusion provided by the counter is meant only
to protect the access to non-logical variables.
At any time, any method call is acceptable.
It is conceivable, however, that whether or not a method call is acceptable
depends on the state of the object, as expressed in its non-logical variables.
A typical example of such an object is the semaphore, given below.
\lprog{sema}{
.ds sema.pl
}
The constructor for sema causes the semaphore to loop
over a conditional that tests the value of the non-logical
variable n.
When the value of n is zero, calls to will be suspended,
because of the statement ;
otherwise both and may occur,
since when n is not zero the statement is
evaluated.
A semaphore of the kind above may be used to regulate the
concurrent evaluation of goals by passive objects.
To illustrate this we present a modified version of
the travel agency described in section 1.2.
\lprog{travel}{
.ds travel3.pl
}
The object travel implements a
multiple readers/single writers protocol,
since adding an item to the city list
is embedded in the semaphore calls and .
The initialization of the non-logical
variable s to an active instance of sema
occurs exactly once for each instance of travel.
Semantics
Let us now take a closer look at the semantics of the accept statement.
We only allow accept statements to occur in
active objects since we wish method calls
for passive objects to be evaluated concurrently.\ftn{
In addition,
for active objects we do allow accept statements to occur
(possibly nested) in processes for evaluating
method calls.
In our semantic description, however,
we impose the requirement that accept statements may occur only in the
constructor process of an object.
}
.so rv
Operationally, when an accept statement is reached,
the evaluation of the current goal is suspended until a method
allowed by the arguments of the accept statement is called for.
We call the argument of the accept statement the
accept list, and by convention take any to stand
for all methods of the object.
When a method not occurring in the accept list is called for,
the call is suspended and the object waits until
another call satisfying the accept list occurs.
The suspended call will result in a process
evaluating the method call whenever the accept list is changed in such a way
that the call is allowed.
To handle suspended calls the object maintains a so-called
accept queue.
All suspended calls join in the accept queue,
in the order in which they arrive.
When the accept list is changed, the object first
searches the queue and takes the first call satisfying
the new accept list.
If no such call is present the object waits for other incoming
calls.
This procedure guarantees fairness in handling method calls
since because of the FIFO behavior of the accept queue,
no allowed call will be suspended forever. C.f. [Am89b].
.so phil