Category Archives: Uncategorized

The less-familiar parts of Lisp for beginners — next-method-p

The next-method-p local function must only be called from within a method definition.  It allows a method to determine whether there is a next method that might be available to call-next-method.  This is helpful, as invoking call-next-method without a next method available will invoke the no-next-method generic function which, unless overridden by a user-supplied method, signals an error.

The less-familiar parts of Lisp for beginners — nconc

We next come to the nconc function.  The quick way to think of this is like append, but modifying its lists rather than creating a new list with the elements of the input lists.  The difference is quite important, and merits some discussion.

Recall that lists in Lisp are what a C++ programmer would call NULL-terminated singly-linked lists.  Each cell in the list consists of two parts, a value part (the car), which can hold any data type, including other lists, and the next-pointer part (the cdr).

When the append function is called on, say, two lists, then a third list is created, one whose value parts of the cells are the same as those of the input lists, but whose next-pointer parts are unique to this new list, as they point to new cells that were created to support the new list.

When the nconc function is called on two lists, the NULL-termination of the first list is changed to point at the head of the second list.  This means that the original list is modified.  The programmer must also take care to avoid the unintentional creation of circular list structures, as these require some careful coding to handle.  Constructs like dolist and mapcar will, left to themselves, loop forever when the list is circular.

Here is some sample code to demonstrate the difference:
 

CL-USER> (defparameter *list-a* (list 1 2 3))
*LIST-A*
CL-USER> (defparameter *list-b* (list 4 5 6))
*LIST-B*
CL-USER> (append *list-a* *list-b*)
(1 2 3 4 5 6)
CL-USER> *list-a*
(1 2 3)
CL-USER> *list-b*
(4 5 6)
CL-USER> (nconc *list-a* *list-b*)
(1 2 3 4 5 6)
CL-USER> *list-a*
(1 2 3 4 5 6)
CL-USER> *list-b*
(4 5 6)
CL-USER> (setf *print-circle* t)
T
CL-USER> (nconc *list-a* *list-b*)
(1 2 3 . #1=(4 5 6 . #1#))
CL-USER> (dolist (var *list-a*)
           (format t "~A " var)
           (sleep 0.2))
1 2 3 4 5 6 4 5 6 4 5 6 4 5 6 4 5 6 4 5 6 4 5 6 4 5 6 4 5 ; Evaluation aborted on NIL.

In the final example, I used the Slime Interrupt Command key sequence CTRL-C CTRL-C, as otherwise the code would have run forever.  The special variable *print-circle* is defined in the standard as allowing the printing of circular objects such as the list we created above.  It uses a different code path to detect cycles and print them in a standard-defined way.

The less familiar parts of Lisp for beginners — summary M

I’ve written a few posts about features of the Lisp language that a newcomer arriving from C++ might not have encountered.  I’m going through them alphabetically, so here is a summary page of the functions beginning with the letter M:

macro-function

macrolet

make-broadcast-stream

make-condition

make-dispatch-macro-character

make-instances-obsolete

make-load-form

make-load-form-saving-slots

make-method

make-package

make-symbol

makunbound

map

mask-field

method-qualifiers

muffle-warning

multiple-value-call

multiple-value-list

multiple-value-prog1

multiple-value-setq

 

The less-familiar parts of Lisp for beginners — multiple-value-setq

One more Lisp feature that newcomers might not have encountered is the multiple-value-setq macro.  While most introductions discuss multiple-value-bind, this feature is similar, but not always described.  The multiple-value-setq macro uses setq to store its values, rather than creating new bindings.  So, think of the relationship between setq and multiple-value-setq as being much like the relationship between let and multiple-value-bind.

The less-familiar parts of Lisp for beginners — multiple-value-prog1

Recently, our attention has been on Lisp features related to multiple values.  As mentioned previously, the values accessor allows the programmer to return zero or more values from a form, unlike C++ where a function can return at most one value.

Certain Lisp constructs encourage the use of progn, a feature that executes several forms in series and returns the values of the last form in the group.  Examples include if, unwind-protect, and any other feature that evalutes exactly one form in some context.  In that case, progn allows the programmer to collect several forms together and build a single form out of them for syntactic purposes.

One way that newcomers to Lisp can get tripped up is to think that prog1 or prog2 are exactly the same as progn, just returning the values from different forms in the series.  In fact, prog1 and prog2 return only the primary value of the corresponding form, they do not return all of the values.

If the programmer has a need to retrieve the multiple values from the first form in prog1, he or she should use multiple-value-prog1.  As suggested by the name, this special operator executes the forms inside it in order, and returns all values of the first form.

So, we’ve got progn, which returns all values.  We have prog1, which returns only the primary value, so we have multiple-value-prog1 to fill that gap.  We also have prog2 which, like prog1, does not return all values, but there isn’t a multiple-value-prog2.

Well, we can always write our own.  Here’s an example of a macro that achieves the effect of multiple-value-prog2:
 

(defmacro multiple-value-prog2 (&body body)
  `(progn
     ,(first body)
     (multiple-value-prog1
         ,@(rest body))))

Note that, with this macro, you get an error at macro expansion time if body does not contain at least two forms, but prog2 has the same behaviour, so I don’t consider that a bug in this macro.

Now, just for completeness, a transcript of some operations involving multiple values, also including the effect of that macro above:
 

CL-USER> (progn 
           (values 1 1) 
           (values 2 2) 
           (values 3 3))
3
3
CL-USER> (prog1
           (values 1 1) 
           (values 2 2) 
           (values 3 3))
1
CL-USER> (prog2
           (values 1 1) 
           (values 2 2) 
           (values 3 3))
2
CL-USER> (multiple-value-prog1
           (values 1 1) 
           (values 2 2) 
           (values 3 3))
1
1
CL-USER> (multiple-value-prog2
           (values 1 1) 
           (values 2 2) 
           (values 3 3))
2
2