Data in Lisp takes the form of symbolic expressions (s-expressions). In order to transfer data between Lisp processes, the data has to be serialised and transferred over the communication channel. Given the basic primitives to send an integer, string or floating point number, we can build up our protocol to handle any type of Lisp data. For each of the basic Lisp types, we assign an integer code. To send the data we first send the integer code so that the receiver knows what to expect. What follows is dependent on the type of the data. For example to send an integer, all that is sent is the code for an integer, followed by the integer itself.
For non-trivial pieces of data such as cons, recursive calls to a
sending function are made. So for example a cons-pair (the basic
unit of list structure in Lisp) consists of
sending the code for cons-pair followed by sending the car
expression (head of the list), then the cdr expression (tail of
the list). This is illustrated in this fragment of code:
(defun send-sexp (socket obj)
(case (class-of object)
(integer (send-int socket int-code) (send-int socket obj))
(cons (send-int socket cons-code) (send-sexp socket (car obj))
(send-sexp socket (cdr obj)))
...))
This strategy works fine, in general, but for cyclic data structures the recursive nature causes an infinite loop. This can be avoided by both the transmitting and receiving processes maintaining a list of nodes sent previously. When a node that has been sent before needs to be sent again, a reference to the earlier transmission is sent instead. The cache on the transmission end is based on a hash table to permit fast lookup based on the node itself; on the receive end it is a vector whose elements can be referenced in constant time.
In general, cyclic user data structures are not common, but there are two important exceptions in an object-oriented Lisp interpreter: functions and objects. Typically when we consider transporting functions, we need to consider the environment that the code was defined in (i.e. the values of variables external to a function but in scope when the function was defined), and this may be cyclic; similarly, implementations of objects and classes can contain cyclic structures. The transmission of objects is a similar problem to saving an object to a file, as pointed out by Tierney [5].