Our next obscure function is makunbound. This works exactly like fmakunbound, but it causes the value field of the symbol, rather than the function field, to become undefined.
Tag Archives: obscure commands
The less-familiar parts of Lisp for beginners — make-symbol
Next, we come to the make-symbol function. The interested reader might want to review the earlier articles about packages and symbols, and about gensym. So, make-symbol builds a new symbol object, but does not intern it. Depending on the needs of the programmer, the symbol can be interned, or it can be used uninterned. Even uninterned symbols are useful, they can be used as local variables in macro expansions, as those that are returned by gensym, and they will never be eq to any other symbol, even another interned or uninterned symbol with the same name:
CL-USER> (let ((sym-1 (make-symbol "ABC")) (sym-2 (make-symbol "ABC")) (sym-3 (intern "ABC"))) (format t "(eq sym-1 sym-2): ~A~%" (eq sym-1 sym-2)) (format t "(eq sym-1 sym-3): ~A~%" (eq sym-1 sym-3)) (format t "(eq sym-2 sym-3): ~A~%" (eq sym-2 sym-3))) (eq sym-1 sym-2): NIL (eq sym-1 sym-3): NIL (eq sym-2 sym-3): NIL NIL
The less-familiar parts of Lisp for beginners — make-package
Continuing with Lisp features the newcomer to the language might not have encountered is make-package. This is a function that is rarely called for, other than in its use in the implementation of the defpackage macro. You should use defpackage whenever that is appropriate. The make-package function is useful in defining new packages programmatically at run time, as opposed to the more common case of declaring and defining packages in the source code.
The less-familiar parts of Lisp for beginners — make-method
Now we continue to make-method. This is an example of a Lisp feature with a very narrow scope of use. See the earlier article about define-method-combination for the only legal context in which this feature may be used by the programmer.
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.