Monthly Archives: March 2014

The less-familiar parts of Lisp for beginners — make-load-form-saving-slots

Having just presented a brief introduction to make-load-form, we go on to make-load-form-saving-slots.  This will be more of a continuation of the previous article, rather than a new discussion, so I’d recommend that you review that one before reading this text.

The example I gave in make-load-form was quite simple, and in fact lends itself well to using make-load-from-saving-slots.  In effect, make-load-form-saving-slots is a helper function that simplifies the writing of make-load-form functions.  If the class can be regenerated simply by recopying the contents of its slots, then make-load-form-saving-slots takes care of that.  So, let’s go back and look at our 2dpt class, and see what happens when we feed it to make-load-form-saving-slots.
 

CL-USER> (make-load-form-saving-slots (make-instance '2dpt))
(ALLOCATE-INSTANCE (FIND-CLASS '2DPT))
(PROGN
 (SETF (SLOT-VALUE #<2DPT {100323A443}> 'X) '0.0d0)
 (SETF (SLOT-VALUE #<2DPT {100323A443}> 'Y) '0.0d0))

Now, it may not be clear here, but this is returning two values.  So, this series of posts is directed at newcomers to Lisp, and even so I imagine you’ve encountered multiple return values before, but just in case this is new, it’s time for a short digression.

Forms in Lisp return zero or more values.  The common construction returns exactly one value, but by use of the values accessor it is possible to return 0 values, or several.  In many contexts, only the first value, the primary value, will be retained, but with macros like multiple-value-bind and multiple-value-list it is possible to recover all of the values returned from the form.

The example we gave of make-load-form returned only one value, so the second value is implicitly nil, no action.

OK, with that aside out of the way, why is my form so different than this generated form?  I’ll put them together for comparison:
 

CL-USER> (values (make-load-form (make-instance '2dpt)) 
                 nil)
(MAKE-INSTANCE '2DPT :X 0.0d0 :Y 0.0d0)
NIL
CL-USER> (make-load-form-saving-slots (make-instance '2dpt))
(ALLOCATE-INSTANCE (FIND-CLASS '2DPT))
(PROGN
 (SETF (SLOT-VALUE #<2DPT {10036A2263}> 'X) '0.0d0)
 (SETF (SLOT-VALUE #<2DPT {10036A2263}> 'Y) '0.0d0))

In fact, make-load-form returns two values.  The first value is the “creation form”, the second is the “initialization form”.  Creation forms may not contain circular references, while initialization forms may, so there is value in splitting them out this way.  For the simple case here, the end result is the same, but for more complicated cases the distinction is important.  You’ll note, also, that my simple implementation uses make-instance, while the other one uses allocate-instance.  You should review the discussion under allocate-instance to understand the difference, but in C++ terms make-instance calls constructor-like functions while allocate-instance simply sets aside the storage.

Now, I mentioned circular references.  What do I mean, and what is the impact?  Well, let’s look at the dl-list structure I created while discussing macros a long time ago.  I’m not going to reproduce the whole file here, just some definitions to provide context.  Please review the earlier article for the full source code.
 

(defpackage :DL-LIST
  (:use :COMMON-LISP)
  (:export :EMPTY-P :DL-LENGTH :ITER-FRONT :ITER-BACK 
           :ITER-NEXT :ITER-PREV :PUSH-FRONT :PUSH-BACK
           :POP-FRONT :POP-BACK :ITER-CONTENTS
           :INSERT-AFTER-ITER :MAKE-DL-LIST))

(in-package :DL-LIST)

;; Define the node.  They're opaque types to the user, but 
;; they double as iterators (also opaque).
(defstruct (dl-node)
  (value        nil)
  (next-node    nil)
  (prev-node    nil))

;; Define the doubly-linked list.
(defstruct (dl-list (:conc-name dlst-))
  (first-node   nil)
  (last-node    nil))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;   Etcetera
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Now, there are two ways we can make a load form for a dl-list.  One is the brute-force way, recreating the structures by filling the slots with their appropriate values, by making a load form for both dl-list and dl-node:
 

(defmethod make-load-form ((obj dl-list) &optional environment)
  (declare (ignore environment))
  `(dl-list:make-dl-list :first-node ',(dlst-first-node obj)
                         :last-node ',(dlst-last-node obj)))

(defmethod make-load-form ((obj dl-node) &optional environment)
  (declare (ignore environment))
  (values
   `(dl-list::make-dl-node)
   `(setf (dl-list::dl-node-value) ,(dl-list::dl-node-value obj)
          (dl-list::dl-node-next-node) ,(dl-list::dl-node-next-node obj)
          (dl-list::dl-node-prev-node) ,(dl-list::dl-node-prev-node obj))))

This works:
 

CL-USER> (defparameter *dlist* (dl-list:make-dl-list))
*DLIST*
CL-USER> (dl-list:push-front *dlist* 10)
{ NIL <- 10 -> NIL }
CL-USER> (dl-list:push-front *dlist* 9)
{ NIL <- 9 -> 10 }
CL-USER> *dlist*
#S(DL-LIST::DL-LIST
   :FIRST-NODE { NIL <- 9 -> 10 }
   :LAST-NODE { 9 <- 10 -> NIL })
CL-USER> (make-load-form *dlist*)
(DL-LIST:MAKE-DL-LIST :FIRST-NODE '{ NIL <- 9 -> 10 } :LAST-NODE
                      '{ 9 <- 10 -> NIL })
CL-USER> (eval (make-load-form *dlist*))
#S(DL-LIST::DL-LIST
   :FIRST-NODE { NIL <- 9 -> 10 }
   :LAST-NODE { 9 <- 10 -> NIL })
CL-USER> (eq *dlist* (eval (make-load-form *dlist*)))
NIL

As you can see, I create a new dl-list and fill it with the sequence 9,10.  Then I make a load form from it, and eval it (I can do this because the dl-list load form does not have an initialization form, only a creation form).  The result of the eval is a new dl-list, with the same contents, but not eq to the original one.  So, I’ve created a copy of the dl-list.  It’s important that the filling in of the slots in the dl-node structures be done in the initialization form, not the creation form, because of the circularity that results from following the next pointer of the previous pointer of a node.

However, there is another way to make the load forms, one that’s arguably better.  The other way relies on the external API of the dl-list structures, rather than their internal implementations:
 

(defmethod make-load-form ((obj dl-list) &optional environment)
  (declare (ignore environment))
  (values
   `(dl-list:make-dl-list)
   `(progn
      ,@(let (contents)
             (iter-loop (obj iter)
               (push (iter-contents obj iter) contents))
             (mapcar #'(lambda (x)
                         `(dl-list:push-front ,obj ,x))
                     contents)))))

What this does is builds a load form by reading out the contents of the dl-list, and then returning a form that inserts into the dl-list using the external symbols of the package.
 

CL-USER> (defparameter *dlist* (dl-list:make-dl-list))
*DLIST*
CL-USER> (dl-list:push-front *dlist* 10)
{ NIL <- 10 -> NIL }
CL-USER> (dl-list:push-front *dlist* 9)
{ NIL <- 9 -> 10 }
CL-USER> (make-load-form *dlist*)
(DL-LIST:MAKE-DL-LIST)
(PROGN
 (DL-LIST:PUSH-FRONT
  #S(DL-LIST::DL-LIST
     :FIRST-NODE { NIL <- 9 -> 10 }
     :LAST-NODE { 9 <- 10 -> NIL })
  10)
 (DL-LIST:PUSH-FRONT
  #S(DL-LIST::DL-LIST
     :FIRST-NODE { NIL <- 9 -> 10 }
     :LAST-NODE { 9 <- 10 -> NIL })
  9))

That means that I can change the internal implementation of the dl-list and dl-node structures, and as long as I don’t change the API manipulators, this make-load-form function will continue to work.  I’ve used an initialization form separate from the creation form in case a dl-list contains references to other objects that refer back to the dl-list.  That would be a circular reference and would cause an error if all of the dl-list:push-front invocations were in the creation form.  One inconvenience, though, is that eval at the REPL won’t demonstrate the correctness of this because it will only evaluate the creation forms by default.  So, to show that this is working, I add a form at the bottom of the input file and compile it:
 

(let ((test-case 
       #.(let ((rv (dl-list:make-dl-list)))
           (dl-list:push-front rv
                               (get-universal-time))
           (dl-list:push-front rv
                               9)
           rv)))
  (format t "~A~%" test-case))

This added form will cause the Lisp instance to generate output when the file is loaded.  To show that the object was copied from its form at compile time, here it is in context:
 

CL-USER> (progn
           (format t "~D~%" (get-universal-time))
           (load "dllist-v5")
           (format t "~D~%" (get-universal-time)))
3603308753
#S(DL-LIST
   :FIRST-NODE { NIL <- 9 -> 3603308724 }
   :LAST-NODE { 9 <- 3603308724 -> NIL })
3603308753
NIL

So, that’s the second half of our discussion of make-load-form.  As we showed, make-load-form-saving-slots is useful, in certain fairly simple applications, but the more general make-load-form, properly used, can help to insert objects generated once, at compile time, and so avoid the cost of regenerating those objects at load-time or run-time.

The less-familiar parts of Lisp for beginners — make-load-form

We continue this series of Lisp features which the newcomer from C++ might not have encountered with make-load-form.  This is a standard generic function, like print-object, and like print-object, the programmer may find contexts in which it is helpful to specialize this function on a programmer-defined class.

If you recall the earlier article on load-time-value, we had a way for the programmer to build objects as the Lisp code was being read into the Lisp instance, rather than at execution time.  In this way, objects can be built once, as the code is loaded, rather than every time the form is encountered during normal execution.  However, there is another option.  What if the object could be built during compilation?  Now, you don’t even need to incur the cost of building the instances at load time, you can generate the instances once, as the code is being compiled, and then not have to incur the cost of regenerating the instances every time the Lisp files are loaded into a Lisp instance.

So, one way you might do this is with the read-macro #..  That’s typographically awkward, it’s the ‘#’ character followed by a single period.  This read-macro evaluates the form that follows it and inserts the result into the code at that point.  Here’s an example of this use:
 

(defun times ()
  (let ((current-time (get-universal-time))
        (read-time #.(get-universal-time)))
    (format t "Current time= ~D~%" current-time)
    (format t "Read time= ~D~%" read-time)))

When this defun form is passed to the Lisp instance, an interesting thing happens.  The #. read-macro causes the form (get-universal-time) to be evaluated at read time, when the defun is encountered, and the returned value is inserted as a literal constant into the code.  That means that, as far as the function is concerned, current-time is assigned at invocation time, by calling a function, but read-time is assigned from an integer constant, one that does not change as you call the function several times.  Here’s an example output:
 

CL-USER> (dotimes (i 3)
           (format t "-----------------~%")
           (sleep 2)
           (times))
-----------------
Current time= 3603210003
Read time= 3603209874
-----------------
Current time= 3603210005
Read time= 3603209874
-----------------
Current time= 3603210007
Read time= 3603209874
NIL

As you can see, the read time remains fixed even as the current time is updated.

So, this works well.  If you compile the file, the read-time variable is fixed to the time when the form was read in to begin the compilation of that function.  The read-time has effectively become the compile time.  Now, what if you wanted to do this with an object?  I’ll go through this slowly, starting with some incorrect examples and explaining the problems as we go.

We define our own class of 2-dimensional points and build instances of them at read time.  Here’s the file:
 

(defclass 2dpt ()
  ((x           :accessor get-x
                :initarg :x
                :initform 0.0d0)
   (y           :accessor get-y
                :initarg :y
                :initform 0.0d0)))

(defun distance (pt1 pt2)
  (let ((delta-x (- (get-x pt2) (get-x pt1)))
        (delta-y (- (get-y pt2) (get-y pt1))))
    (sqrt (+ (* delta-x delta-x) (* delta-y delta-y)))))

(defun demonstrate ()
  (let ((offset #.(make-instance '2dpt :x 0.0d0 :y 0.0d0))
        (testpt #.(make-instance '2dpt :x 1.0d0 :y 2.0d0)))
    (format t "Distance= ~F~%" (distance testpt offset))))

We can load this .lisp file, and run the demonstrate function, it seems fine.  So, now we decide it’s time to compile it in a fresh Lisp instance:
 

CL-USER> (compile-file "make-load-form.lisp")
; compiling file "make-load-form.lisp" (written 08 MAR 2014 02:52:16 PM):
; compiling (DEFCLASS 2DPT ...)
; compiling (DEFUN DISTANCE ...)
; 
; caught ERROR:
;   READ error during COMPILE-FILE:
;   
;     There is no class named COMMON-LISP-USER::2DPT.
;   
;     (in form starting at line: 12, column: 57, file-position: 364)
; 
; compilation unit aborted
;   caught 1 fatal ERROR condition
;   caught 1 ERROR condition
; compilation aborted after 0:00:00.011
NIL
T
T

So, what happened?  Why is this an error?  Well, we’re compiling the file, not loading it.  The defclass form is not evaluated, so when the read-macro calls make-instance on the 2dpt class, there is no record of a class by that name in the Lisp image.  That results, naturally, in an error.

So, we need to make the defclass visible to the compiler.  We covered eval-when earlier in this series.  Here’s the modification:
 

(eval-when (:compile-toplevel :load-toplevel)
  (defclass 2dpt ()
    ((x         :accessor get-x
                :initarg :x
                :initform 0.0d0)
     (y         :accessor get-y
                :initarg :y
                :initform 0.0d0))))

(defun distance (pt1 pt2)
  (let ((delta-x (- (get-x pt2) (get-x pt1)))
        (delta-y (- (get-y pt2) (get-y pt1))))
    (sqrt (+ (* delta-x delta-x) (* delta-y delta-y)))))

(defun demonstrate ()
  (let ((offset #.(make-instance '2dpt :x 0.0d0 :y 0.0d0))
        (testpt #.(make-instance '2dpt :x 1.0d0 :y 2.0d0)))
    (format t "Distance= ~F~%" (distance testpt offset))))

Now, we compile the file:
 

CL-USER> (compile-file "make-load-form.lisp")
; compiling file "make-load-form.lisp" (written 08 MAR 2014 03:02:00 PM):
; compiling (DEFCLASS 2DPT ...)
; compiling (DEFUN DISTANCE ...)
; compiling (DEFUN DEMONSTRATE ...)

; file: make-load-form.lisp
; in: DEFUN DEMONSTRATE
;     (LET ((OFFSET #<2DPT {1005CAF253}>) (TESTPT #<2DPT {1005CB3A43}>))
;       (FORMAT T "Distance= ~F~%" (DISTANCE TESTPT OFFSET)))
; ==>
;   #<2DPT {1005CAF253}>
; 
; caught ERROR:
;   don't know how to dump #<2DPT {1005CAF253}> (default MAKE-LOAD-FORM method called).

; ==>
;   #<2DPT {1005CB3A43}>
; 
; caught ERROR:
;   don't know how to dump #<2DPT {1005CB3A43}> (default MAKE-LOAD-FORM method called).
; 
; note: deleting unreachable code
; 
; note: deleting unreachable code
; 
; compilation unit finished
;   caught 2 ERROR conditions
;   printed 2 notes

; make-load-form.fasl written
; compilation finished in 0:00:00.013
#P"make-load-form.fasl"
T
T

What happened this time?  Well, we’ve written code to build 2dpt objects at read time during the compilation.  Those objects are to be inserted directly into the function where they appear.  However, the compiler is responsible for producing code that can be read in later, in a different Lisp image, so it has to know how to store instances of the 2dpt object in such a way that they can be reliably reconstructed during the later load.  It can’t just deposit a binary copy of the instance, there might be references to other objects inside it, and those references can’t generally be bitwise-copied across instances.  In C++ terms, if you are attempting to store an object to disk and expect to be able to re-read it in a later run of the program, you’re going to have to figure out what to do about pointers in the object, which will almost certainly be nonsensical when the object is loaded later.  This is the same reasoning that applies when you write copy-constructors in C++.

So, think of make-load-form as a kind of way of defining copy-constructors for use by the compiler.  Now, we insert that into the code and try again:
 

(eval-when (:compile-toplevel :load-toplevel)
  (defclass 2dpt ()
    ((x         :accessor get-x
                :initarg :x
                :initform 0.0d0)
     (y         :accessor get-y
                :initarg :y
                :initform 0.0d0)))

  (defmethod make-load-form ((obj 2dpt) &optional environment)
    (declare (ignore environment))
    `(make-instance '2dpt :x ,(get-x obj) :y ,(get-y obj))))

(defun distance (pt1 pt2)
  (let ((delta-x (- (get-x pt2) (get-x pt1)))
        (delta-y (- (get-y pt2) (get-y pt1))))
    (sqrt (+ (* delta-x delta-x) (* delta-y delta-y)))))

(defun demonstrate ()
  (let ((offset #.(make-instance '2dpt :x 0.0d0 :y 0.0d0))
        (testpt #.(make-instance '2dpt :x 1.0d0 :y 2.0d0)))
    (format t "Distance= ~F~%" (distance testpt offset))))

Now, we compile the code again, and this time it compiles cleanly, without errors or warnings.  And that’s a basic look at make-load-form, but as this is getting long, we’ll look at it in more detail in the next article on make-load-form-saving-slots, where we will see some more advanced behaviours that are interesting to go over.

The less-familiar parts of Lisp for beginners — make-instances-obsolete

Our next stop in the Lisp features that newcomers arriving from C++ might not be aware of is make-instances-obsolete.  We’ve touched on this capacity briefly before, in this article.

Once again, this returns to a fundamental difference between C++ programs and Lisp programs, one that I’ve mentioned several times before in this series, but which I will repeat here.

C++ programs are written, compiled, and executed.  Barring implementation-defined tricks like shared object plugins, if the C++ programmer wants to make a change to the code, he edits the source, recompiles it, and then shuts down the running version of the program, and then restarts the program using the changed binary.

In Lisp, functions and data are added to a Lisp instance, and the “program” can be modified by updating functions in the Lisp, without shutting it down and restarting.  One possible change is to modify the definition of a class, which can be done even if there are instances of the class present in the Lisp instance.  When the class is modified in such a way as to change the slots (the equivalent of members in C++ structs or classes), then make-instances-obsolete is automatically invoked on the class, and all instances are marked for updating.  At some implementation-defined time later, but before the next access to a slot in a specific instance, the method update-instance-for-redefined-class is called on the instance to make any required changes to bring an instance constructed in the old definition into compliance with the new definition of its class.  The programmer can also call make-instances-obsolete explicitly, in those cases where the automatic invocation would not be performed because the slots are unchanged.

The less-familiar parts of Lisp for beginners — make-dispatch-macro-character

We next arrive at make-dispatch-macro-character.  I would suggest that the new Lisp user review the discussion on readtables and read-macros that can be found in the gensym and get-dispatch-macro-character articles.

The make-dispatch-macro-character function creates a new dispatching macro character (i.e. the first character of two-character read macros) for the read table.  It is initially constructed with all sub-characters set to signal errors.  Once make-dispatch-macro-character has built this new dispatch, the individual sub-characters can be assigned with set-dispatch-macro-character.