[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Issue: PRINT-CIRCLE-STRUCTURE (Version 1)



I discovered that I had neglected to forward this issue, for
which I apologize. 

!
Issue:         	PRINT-CIRCLE-STRUCTURE
References:    	pp. 370-371
Category:      	CLARIFICATION
Edit history:	Version 1.0, Chris McConnell 9/20/88

Problem description:

When a print function for a structure is defined using the
:print-function option to defstruct, circular references are no longer
printed with #n# syntax even though *print-circle* is T.  This
prevents printing structures that point to objects that cannot be
printed and prevents the development of new printed representations
that can be read by the reader.

Proposal PRINT-CIRCLE-STRUCTURE: 

When *print-circle* is T, the printer should detect and print
circularities using the #n# syntax even if a structure has a user
defined print-function.  Circularities need only be detected for
objects that would normally be printed by the default structure
print-function.  A user defined print-function can print any object
that is pointed to by the structure being printed, or any object that
would normally be printed as a component of such an object and expect
circularities to be detected.

Test Cases/Examples:

;;;
;;; Define a structure that can be circular and that has a slot with a
;;; value that cannot be printed.
;;;
(defstruct (TEST (:print-function print-test))
  ptr
  (function #'(lambda (x) 
		(error "No function is defined."))))

;;;
;;; This print function generates a machine readable printed
;;; representation for a structure with a slot that cannot be printed.
;;;
(defun PRINT-TEST (structure stream depth)
  (format stream "#S(TEST PTR ~A)" (test-ptr structure)))

;;;
;;; Define two structures that point to each other.  If this
;;; expression successfully evaluates at the top level, then the
;;; printed result should look like:
;;; #1=#S(TEST PTR #S(TEST PTR #1#))
;;;
;;; If it does not work then it will generate an infinite printed
;;; representation. 
(setf *print-circle* t
      *a (make-test)
      *b (make-test)
      (test-ptr *a) *b
      (test-ptr *b) *a)

Rationale:

Many structures are circular and point to complex data structures that
may include things like closures that cannot be printed.  It should be
possible to define a way to print these data structures such that they
can be read back in.  Common LISP provides two mechanisms for these
problems (*print-circle* and the :print-function option to defstruct),
but they do not currently work together.

Current practice:

None of the Common LISPs that I have worked with currently implement
this proposal.  That includes Symbolics 7.2, Lucid 3.0, KCL, Coral and
Franz.

Cost to Implementors:

I believe that it is a rather straightforward change.  All of the
implementations detect circularities for the default structure
printer.  All that is required is to perform the same circularity
check and printing #n= before or #n# instead of what would normally be
generated by the user defined print-function.

Cost to Users: None

Cost of non-adoption: 

If nothing is done, the whole effort to provide a portable printed
representation of LISP structures is of minimal usefulness.  In almost
any real application, there are circular structures with non printable
objects such as closures and hash tables that need to be printed.  In
addition the development of printers for new reader macros becomes
much more of an effort then it should be since it requires
reimplementing the complete circularity detection mechanism.

Benefits:

If the proposal is adopted, then it becomes easier to define new
printed representations that are compact and that still capture the
information needed to rebuild data structure instances.  It allows a
printed representation to hide the actual details of how a data
structure is implemented in terms of underlying LISP data structures.
For an example see the discussion section.

Esthetics: 

This proposal increases the uniformity of the language by making
*print-circle* work in all cases including where a user has defined a
new print function.

Discussion:

I first came across this problem in trying to generate a printed
representation for CLOS instances.  The current PCL implementation of
CLOS implements an instance as a structure with four slots.  One of
the slots points to an array that stores information about the class
that the structure is an instance of.  That array points to a number
of other data structures that cache various information and that point
to other data structures some of which point to closures.  If you try
to print an instance using the #S syntax, most printers will blow up
because eventually, you try to print a closure.  In addition, there is
all sorts of extraneous detail that does not really belong in a
printed representation of an instance.  A #I reader syntax can easily
be defined that represents a CLOS instance as a class name and a list
of slot names and values much like the #S syntax.  When this is done
by defining a structure print-function, the circularity detector no
longer detects circularities.  The only way to get around this is to
write your own circularity detector that works for all LISP objects.
This can be done, but it seems like a lot of effort when the exact
mechanism that is needed is already built into Common LISP.

Part of the reason for wanting to be able to do things like this is to
be able to generate files that have a compiled representation of data
structures in them.  To do this, you put something like:

(setq *a '#.*a)

in a file and then you compile it in an environment where *a is bound
to the data structure you would like to save in the file.  The result
is a compiled file that contains the data structure that you want.  In
order for this to work, some compilers use the printer mechanism to
generate a printed form of the data structure and then compile that.
This use of the printed representation of objects could be alleviated
if there existed some mechanism equivalent to the
sys:dump-forms-to-file function provided by Symbolics.