Category Archives: Uncategorized

The less-familiar parts of Lisp for beginners — update-instance-for-redefined-class

With the update-instance-for-redefined-class generic function, we encounter, once again, the concept of redefined classes in Lisp.  This has no parallel in C++, the programmer changes the slots within a class even while there exist instances of the class.

The programmer should not call this function directly, it is invoked by the system when necessary.

This generic function behaves much like update-instance-for-different-class, and most of what was described there is true here, but with one important exception.  In the former case, the original and new class definitions both existed, so objects of both types could be present at the same time, and the accessors for the original class were still available.  Now, the redefined class means that the original class definition is no longer valid, so the code cannot examine it through any of the normal methods.  The accessors are no longer available, and slot-value has nothing to work on.  As the programmer might need to examine the contents of slots that have been deleted in the class redefinition, there is a mechanism for that in the invocation of this generic function.  They are supplied in a property list that is passed to this method.

Once again, by writing a specialized :after method on this generic function, the programmer can examine the property list and take action based on the values in discarded slots.

The less-familiar parts of Lisp for beginners — update-instance-for-different-class

We’re back to another function that has no real analogue in C++.  The update-instance-for-different-class generic function is not to be called by the programmer, but it is still useful to know about it, as the programmer can specialize an :after or :before method to take special action.

In Lisp, it is possible to convert an instance to a new type with the change-class generic function.  This was demonstrated in an earlier article, along with a specialized :before method for update-instance-for-different-class that allowed me to display the contents of the object prior to its being changed.

What are the cases when the programmer might want to specialize this generic function?  Well, the default behaviour of update-instance-for-different-class is to copy slots with the same name from the old to the new instance and initialize new slots according to their initarg or initform forms, if any.  Slots in the old class whose names do not appear in the new class have their values discarded.  A :before method allows the programmer to examine those slots soon to disappear and take action based on their contents.  An :after method, on the other hand, has access both to the original instance and to the post-copy, post-initialize slots in the new class, and can take further action to initialize or change slots based on those values.

The less-familiar parts of Lisp for beginners — unwind-protect

While this is not really in the realm of uncommon commands, being a fairly useful and frequently-used feature, I want to point it out for the C++ programmers who are new to Lisp, just to reinforce its usefulness.

In both C++ and Lisp, a function or block can exit in some non-trivial ways.  Flow could jump out of the block with a goto, or a called function could throw an exception that is only caught somewhere higher up.  Lisp has equivalents to those.  There is no setjmp()/longjmp() in Lisp, but the C++ programmer should consider himself or herself forbidden from using those, they do bad things.

There will be times when the program does something, and needs to have a guaranteed method of undoing it no matter how execution passes out of the block.  In C++, this is commonly done by putting the shutdown behaviour in an object destructor and instantiating the object on the stack as an auto variable.  So, where in C one writes fopen() and fclose() on a file, in C++ one commonly uses iostream objects whose destructors close the file descriptor associated with the open file.  In this way, the programmer can avoid leaking file descriptors when an exception exits the function after the file has opened, but before it has closed.

An alternative in C++ is to enclose everything in a try{} block, and do the cleanups in the corresponding catch, then re-throw the exception.  This is a bit klunky, though, and does require that the closing code is written twice, once in the regular flow, and once in the exception caught case.

In Lisp, there are no object destructors, so that mechanism isn’t available.  Instead, in Lisp the programmer typically uses unwind-protect.  This special operator is a bit different from the C++ try/catch sequence, because the unwind form is executed in all cases, not just in the exception case.  It essentially says that, however you exit the protected form, immediately execute the cleanup form before proceeding further.  If you want to think of it a C++ try/catch with an implicit catch of any exception followed by a re-throw, you can, but note that important distinction, the cleanup form is executed even in the normal flow, then the block exits normally.

The less-familiar parts of Lisp for beginners — unuse-package

The unuse-package function modifies the Lisp image, causing external symbols from one or more packages that have been inherited into another package (by the use of the complementary function use-package) to stop being available by inheritance within that other package.  This is fairly straight-forward, but it is important to note that this function affects only symbols inherited through use-package, it does not affect symbols explicitly brought in with import.

The less-familiar parts of Lisp for beginners — typecase

Next, we look at the typecase macro.  As Lisp does not enforce types of variables, it is valid for different code paths to set a particular variable or parameter to different data types.  The programmer may want to write code to handle all the cases.  Whereas in C++ one typically would overload a function based on its parameter types, with the compiler assigning the correct function at compile time, in Lisp one would branch code paths based on the run-time determined parameter type.

Now, in C++, it’s common for one function to be the actual worker, and the other functions to be thin wrappers around it.  The non-worker functions simply convert the arguments into a form suitable for processing by the single worker function.  This avoids unnecessary duplication of code and mysterious inconsistencies when one function is modified or debugged while leaving the other functions alone.  In Lisp, the similar construct is to recurse into itself with the modified types.  Here is an example:
typecase.lisp

(defstruct moments 
  (sum-x        0.0d0)
  (sum-x2       0.0d0))

(defun add-values (moment-struct &rest to-add)
  (dolist (one-to-add to-add)
    (typecase one-to-add
      (number
       (incf (moments-sum-x moment-struct) one-to-add)
       (incf (moments-sum-x2 moment-struct) (* one-to-add
                                               one-to-add)))
      (cons
       (dolist (entry one-to-add)
         (add-values moment-struct entry)))
      (vector
       (dotimes (i (length one-to-add))
         (add-values moment-struct (aref one-to-add i))))))
  moment-struct)
                    

The output from a typical set of calls:
*slime-repl sbcl*
CL-USER> (add-values (make-moments) '((1 2) (3 (4 5)) 6))
#S(MOMENTS :SUM-X 21.0d0 :SUM-X2 91.0d0)
CL-USER> (add-values (make-moments) '((1 2) (3 (4 5)) 6 nil))
#S(MOMENTS :SUM-X 21.0d0 :SUM-X2 91.0d0)
CL-USER> (let ((ones-vec (make-array 5 :initial-element 1)))
           (add-values (make-moments) ones-vec))
#S(MOMENTS :SUM-X 5.0d0 :SUM-X2 5.0d0)

You’ll note that the add-values function I’ve written ignores any arguments that are not list, vector, or numeric.  It may be desirable, instead, to use ctypecase or etypecase to signal correctable or non-correctable errors, respectively, when an unexpected type is passed.  That is typically the way I code such constructs, I prefer programs to error out than to silently ignore data passed to them, but how one wants to do this will depend on context and the requirements of the code.

Finally, I’ll point out one other place where I’ve used typecase in the past, when writing macros.  You may have seen an earlier series of posts that I put up on creating macros for a doubly-linked list structure related to my work.  In my context, I needed an additional parameter on the looping macro, for the direction.  The code had to behave slightly differently for forward and backward looping, specifically when building the increment function and loop exit condition.  When the direction parameter was :FORWARD, some code was active, and when :REVERSE, other code was active.  Often, that parameter was explicit in the code, sometimes it was the value of a symbol at runtime.  When the compiler encounters a literal :FORWARD during macro expansion, it can determine that the reverse code path cannot be executed, and issues a warning about unreachable code.  I don’t like my working code to generate warnings, they clutter the output and obscure problems.  So, to avoid the warnings when literal keywords are used, I used typecase at macro expansion time.  Here’s a piece of that code.  You’ll note that the typecase is not in a backtick, so it is evaluated at macro expansion time.  If the compiler determines that the direction parameter is a keyword, it inserts the appropriate single piece of code.  If it is not a keyword, macro expansion falls through to the lower block which evaluates the direction parameter at runtime and then invokes the appropriate macro expansion.
typecase.lisp

(defmacro iter-loop-open-interval ((dll iter start end 
                                        &key (dirxn :FORWARD))
                                   &body body)
  "Loop through the dll while 'iter' ranges from from start+1 to end-1."
  (let (first increment last)
    (typecase dirxn
      (keyword
       (ecase dirxn
         (:FORWARD
          (setf first `(dl-list:iter-dir ,dll 
                                         ,start 
                                         :PLUS 
                                         :circular t))
          (setf increment `(iter-dir ,dll 
                                     ,iter 
                                     :PLUS 
                                     :circular t))
          (setf last `(dl-list:iter-dir ,dll 
                                        ,end 
                                        :MINUS 
                                        :circular t)))
         (:REVERSE
          (setf first `(dl-list:iter-dir ,dll 
                                         ,start 
                                         :MINUS 
                                         :circular t))
          (setf increment `(iter-dir ,dll 
                                     ,iter 
                                     :MINUS 
                                     :circular t))
          (setf last `(dl-list:iter-dir ,dll 
                                        ,end 
                                        :PLUS 
                                        :circular t))))

       `(unless (or (eq ,start ,end)
                    (eq ,start ,last))
          (do ((,iter ,first ,increment))
              ((eq ,iter ,end))
            ,@body)))

      (t
       `(ecase ,dirxn
          (:FORWARD
           (iter-loop-open-interval (,dll ,iter ,start ,end 
                                          :dirxn :FORWARD)
                                    ,@body))
          (:REVERSE
           (iter-loop-open-interval (,dll ,iter ,start ,end 
                                          :dirxn :REVERSE)
                                    ,@body)))))))