Information Processing North-Holland
Letters
REAL-TIME
FUNCTIONAL
W.F. CLOCKSIN Programming
8 November
17 (1983) 173-175
QUEUE OPERATIONS
USING THE LOGICAL
VARIABLE
1983
*
**
Reseurch Group, Unwersity of Oxf&-d, Keble Road, Oxford OXI SQD,
United Kingdom
Communicated by H. Whitfield Received 18 April 1983 Rewed 11 July 1983
Keywords:
Data structures,
functional
programming,
logic programming,
1. Introduction )Iood and Melville [3] describe an efficient functional (no side-effects) implementation of FIFO queue operations, in which it is not necessary to copy the entire queue for each insertion operation. The implementation allows constant time access to both ends of the queue, but the withdrawal of an element from the queue requires the reversal of a list of length k when k insertions have been performed since the previous withdrawal. Burton [l] has independently described a similar functional implementation which uses essentially the same trick of deferring reversals as [3]. Hood and Melville [3] also suggest but do not demonstrate the existence of a more complicated real-time version, which would require the list reversal to be distributed over a number of operations. The logical variable, a device available to practitioners of logic programming [4], permits a very simple functional implementation of the queue operations: no reversals or copying are required, and at most two conses are performed per operation. By criteria given by Hood and Melville [3] this would constitute a real-time implementation. In logic programming, data structures (terms) may * This research has been supported
by a GEC Fellowship in Robotics at St. Cross College, Oxford. ** Present address: Computer Laboratory, University of Cambridge, Corn Exchange Street, Cambridge CB2 3QG, United Kingdom. 0020-0190/83/$3.00
Q 1983, Elsevier Science Publishers
queue
contain logical variables which may become instantiated to terms as a result of the unification of terms. Instantiating a variable is not a side-effect but a binding which is made during unification and undone on backtracking; if a variable is instantiated to another variable, then they share subsequent instantiations to either. The power of the logical variable is well appreciated by Prolog [2] programmers, for whom functional solutions to problems such as this queue one are readily accommodated. 2. The method We represent a queue as a linked list of twocomponent records in the conventional way, but we ensure that the link component of the input end (‘back’) of the queue is always a logical variable. Insertions are performed by constructing a record in which the first component is the element to be inserted, and the second component is a logical variable. We then arrange that this constructed record is unified with the variable at the back of the queue, causing a logical instantiation. We implement two queue operations: withdraw, which makes available the front element and the queue with the front element removed; and insert, which makes available the queue formed by enqueueing an element onto the back of the queue. The operations are written here in Prolog [2], representing the queue as a list, and using the standard syntax. To review, the term t or arity n
B.V. (North-Holland)
173
Volume
17, Number
4
INFORMATION
PROCESSING
components a,,. . .,a, is denoted . . , a ,,). Logical variables are distinguished as t(a,, beginning with an upper-case letter, and the anonymous variable (each occurrence of which is considered to have a name distinct from any other variable in the clause) is written as an underscore. The syntactic convenience for representing lists is used, for example: having
nil is written cons(a,
as [ 1,
b) is written
as [a 1b],
cons(a,cons(b,cons(c,
d))) is written
cons(a,cons(b,cons(c,nil)))
is written
as [a, b, c Id] as [a, b, c],
etc. The first implementation single unit clause withdraw([FIQ],F,
of withdraw
The first implementation unit clause
i=rt(Q,
1983
of insert is the single
[E I-], E, Q)
having four arguments. A suitable goal, which inserts ‘d’ at the back of [a, b, c I_], is as follows, where the first argument is the input queue, the second argument must be a variable that is instantiated to the variable at the back of the queue, the third argument is the element to be inserted, and the final argument is the returned queue: insert([a,
b, ~1x1, X, d, Y).
Again, the pattern this diagram
of instantiation
is illustrated
by
Q)
b, c 1_I, X, Y)
will cause X to be instantiated to ‘a’, and Y to be instantiated to the queue [b, c 1_I. Hence, withdraw can be used both to inspect the first element and to make available a queue with the first element deleted. Note the use of the anonymous variable at the back of the queue, which plays no role in this operation. The overhead is to match one list cell. No cells are constructed, and no destructive operations occur. Construction of a cell by Prolog is no more costly than by LISP, and the run-time overhead necessary for maintaining the logical variable behaviour is no more costly than, for example, maintaining LISP lambda variable bindings. The pattern of instantiation can be illustrated by juxtaposing the defining clause and the goal, with arrows showing the bindings:
174
8 November
is the
of three arguments, where the first argument is the list having head F (for ‘front’) and tail Q (for ‘queue’); the next two arguments are instantiated to the front element and the queue without the first element, respectively. A goal of the form withdruw([a,
LETTERS
The functional construction of the queue can be depicted by following the bindings starting from ‘d’, to the element variable E, to where a list cell is constructed, to where it is unified with the input queue. Again, only one cell is constructed to form the new queue, and no destructive operations take place.
3. An improvement The above implementation of insert is restrictive by virtue of requiring the goals that use it to explicitly name the logical variable at the back of the queue, and to use the name as a ‘place holder’ argument of the goal. For most applications this cannot be arranged: often a variable is instantiated to a queue which has been constructed elsewhere, and that variable would be used as the first argument of insert. In this case, the back of the queue cannot be named, and the above definition of insert is useless. A more sophisticated treatment is to represent a queue as the binary term q, of which the first component is a list with a variable at the end as before, and the second component is a variable which is instantiated to
Volume
17, Number
INFORMATION
4
the variable at the end of the list. Thus, the queue carries around a handle to its own end. Generators of queues initialise a queue to possess this property; for example, the term representing a queue of one element would be q([a IX], X). The queue operations must then preserve this property. The modified implementation of withdraw and insert, which again is a legal ready-to-run Prolog program, is as follows: withdraw(q([P
imrr(q(Q,
IQ], V), F, q(Q, V)), [E IV]),
5 q(Q, V)).
Each withdrawal matches one list cell and one q cell, and constructs one q cell. Each insertion matches one q cell, and constructs one list cell and one q cell. If we consider the goal genqueue which instantiates its argument to a queue, and genelement which instantiates its argument to some element, then the conjunction of goals . . . . genqueue (Q) , genelement (X) , insert(Q,
X, N),. . .
will institiate N to the queue formed by inserting whatever element is generated onto whatever queue is generated. The bindings obtained for inserting ‘d’ onto q([a, b, c IX], X) are illustrated as follows:
PROCESSING
LETTERS
8 November
1983
4. Conclusion We have shown how the logical variable, as used in logic programming, can be used to implement purely functional queue operations that require very little overhead. Using the criteria of Hood and Melville [3] this can be considered a real-time implementation. The program is also very small, being composed of two unit clauses.
References [l] F.W. Burton, An efficient functional implementation of FIFO queues, Inform. Process. Lett. 14 (1982) 205-206. [2] W.F. Clocksin and C.S. Mellish, Programming in Prolog (Springer, Berlin. 1981). [3] R. Hood and R. Melville, Real-time queue operations in pure LISP, Inform. Process. Lett. 13 (1981) 50-53. [4] R. Kowalski, Predicate logic as a programming language, Proc. IFIP-74 Congress (1974) pp. 569-574.
175