Category Archives: Uncategorized

The less-familiar parts of Lisp for beginners — change-class

Next in our series of posts the newcomer from C++ might have missed when starting Lisp, we have change-class.  I should point out here that I’ve been a bit sloppy when referring to “Lisp”.  The precise term should be “Common Lisp”, because the original Lisp didn’t come with an object system.  Nonetheless, I’m probably going to use the less precise term a lot.

So, change-class.  This is another one of those commands that has no parallel in C++.  It allows the programmer to convert an object of one class into one of a different class.  The classes do not even have to be related through a class hierarchy.  The change, of course, affects all references to the object, not just the view through the variable to which you applied change-class.

The default behaviour of change-class is to copy the contents of all like-named slots, and use :init forms to fill in the rest.  This, however, is usually not sufficient to meet the programmer’s needs, if all it does is to change the type of the object without significantly altering its contents.  To change the slots, or to fill in new slots based on values in old slots, you define a generic method of update-instance-for-different-class, specialized on the old and new types.  A good example of this operation appears in the CLHS.

Here is an example of code that demonstrates the basic behaviour of change-class.
 

;; Demonstrate change-class

(defclass my-base-class ()
  ((field-A     :accessor get-A
                :initform "Base class field A")
   (field-B     :accessor get-B
                :initform "Base class field B")))

(defclass my-derived-class-1 (my-base-class)
  ((field-C     :accessor get-C
                :initform "Derived class 1 field C")
   (field-D     :accessor get-D
                :initform "Derived class 1 field D")))

(defclass my-derived-class-2 (my-base-class)
  ((field-C     :accessor get-C
                :initform "Derived class 2 field C")
   (field-D     :accessor get-D
                :initform "Derived class 2 field D")))

(defclass unrelated-class ()
  ((field-A     :accessor get-A
                :initform "Unrelated class field A")
   (field-B     :accessor get-B
                :initform "Unrelated class field B")
   (field-C     :accessor get-C
                :initform "Unrelated class field C")))

(defmethod update-instance-for-different-class :before ((old my-derived-class-1) (new my-derived-class-2) &key)
  (setf (slot-value new 'field-C) "Overridden converted string."))

(defun demonstrate-1 ()
  (let* ((obj-1 (make-instance 'my-derived-class-1))
         (obj-1-other-reference obj-1))

    (format t 
            "Starting with an object of type ~A.~%"
            (type-of obj-1))
    (format t 
            "Its 'C' and 'D' fields are ~S and ~S~%"
            (get-C obj-1)
            (get-D obj-1))

    (format t
            "We convert it with change-class.~%")

    (change-class obj-1 'my-derived-class-2)

    (format t
            "It now has type ~A.~%"
            (type-of obj-1))

    (format t 
            "Its 'C' and 'D' fields are ~S and ~S~%"
            (get-C obj-1)
            (get-D obj-1))

    (format t 
            "And another reference to the object has type ~A~%"
            (type-of obj-1-other-reference))))

(defun demonstrate-2 ()
  (let ((base-obj (make-instance 'my-base-class)))
    (format t 
            "Created an object of type ~A~%"
            (type-of base-obj))

    (format t
            "Converting to an entirely new, unrelated class.~%")

    (change-class base-obj 'unrelated-class)

    (format t 
            "Object now has type ~A~%"
            (type-of base-obj))

    (format t
            "It's A and C fields are ~S and ~S~%"
            (get-A base-obj) (get-C base-obj))))

Executing this produces the following output:
 
CL-USER> (demonstrate-1)
Starting with an object of type MY-DERIVED-CLASS-1.
Its 'C' and 'D' fields are "Derived class 1 field C" and "Derived class 1 field D"
We convert it with change-class.
It now has type MY-DERIVED-CLASS-2.
Its 'C' and 'D' fields are "Overridden converted string." and "Derived class 1 field D"
And another reference to the object has type MY-DERIVED-CLASS-2
NIL
CL-USER> (demonstrate-2)
Created an object of type MY-BASE-CLASS
Converting to an entirely new, unrelated class.
Object now has type UNRELATED-CLASS
It's A and C fields are "Base class field A" and "Unrelated class field C"
NIL

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

Our next function for newcomers to Lisp is call-next-method.  This may or may not appear in an introduction to Lisp’s object-oriented system, CLOS.  You might want to review this earlier post for an introduction, including an example of the use of call-next-method.

The call-next-method function causes the program to call another method in the combination of the class.  Recall that, in Lisp, we have generic functions, which you can think of as being like the non-member functions of C++.  There is no equivalent to the member functions of C++.  Still, as these generic functions can be specialized on different classes in an inheritance hierarchy, we have polymorphism based on object type.  This function, in its simple usage, allows you to invoke functions in parent classes.  Where, in C++, you explicitly supply the name of the parent class whose method you want to invoke, in Lisp, by default, it searches out the next less specific method and calls that.

When a class does not inherit from multiple base classes, it is simple to use the default method combination rules.  It is also possible, of course, to define custom method combination rules when necessary, but I’m only going to deal with the defaults here.  You can see the default rules at this page in the CLHS.  Basically, it works as follows:  if there are one or more :around methods, the most specific one is executed, instead of calling the normal methods.  Typically, an :around method calls call-next-method to invoke the primary method, because otherwise the primary method is skipped entirely.  However, if there is a less specific :around method, this is called instead, and so on until there are no remaining :around methods, in which case call-next-method invokes, in order:

  • All of the :before methods in order, from most to least specific.
  • The most specific primary method (which can, itself, call call-next-method to invoke less specific primary methods).
  • All of the :after methods in order, from least to most specific (note, this is opposite to the order of the :before methods).

If call-next-method is called in a primary method, it invokes the next less specific primary method (if any).  The call-next-method function cannot be called from within a :before or :after method.

Check the documentation for details on how the presence or absence of arguments to call-next-method affects the parameters passed to the invoked method.

If the default method combination method is unsuitable, for instance if you want a particular class to call a method specialized on the grandparent class and skip over the method specialized on the parent class, that can be done by modifying the default with define-method-combination, which we will discuss later.

The less-familiar parts of Lisp for beginners — byte

On to more obscure Lisp commands.  The byte command is used to generate a bitrange specifier.  This can then be passed to some other function like ldb or dpb so that manipulations can be made to individual bits within an integer.  This is perhaps best shown with an example:
 

CL-USER> (let ((b1 (byte 16 0))
               (b2 (byte 16 1)))
           (format t "Byte 16 0:  #x~X~%" (dpb #x01234567 b1 0))
           (format t "Byte 16 1:  #x~X~%" (dpb #x01234567 b2 0))
           (format t "Byte 16 1 / 2:  #x~X~%" (/ (dpb #x01234567 b2 0) 2)))
Byte 16 0:  #x4567
Byte 16 1:  #x8ACE
Byte 16 1 / 2:  #x4567

What this sequence says is that we define b1 to be an interval of 16 bits, shifted 0 bits from the right (and so enclosing the 16 least-significant bits in the integer).  We define b2 to be an interval of 16 bits, but shifted by 1 bit from the right, so it encloses the 16 bits immediately above the least-significant bit.  We’ve then used dpb to deposit the least significant 16 bits of the hexadecimal literal #x01234567 into the integer 0.  With b1, it inserts into the least significant bits of the number 0, so the effect is to copy #x4567 into the least-significant position of a number that otherwise has all bits zero.  With b2, the insertion is one bit to the left, producing a new sequence #x8ACE which is twice #x4567, because it is shifted one bit to the left.

The less-familiar parts of Lisp for beginners — block

Continuing the series, another Lisp directive that newcomers arriving from C++ might not immediately encounter is block.  This is a simple flow-control statement which can almost be emulated using a goto in C++.

What block does is to create a named collection of statements, such that flow can immediately exit the block when return-from is used.  In C++, you could imagine putting a label immediately after the last statement in the Lisp block, and then using goto to branch to that label.  This is not quite an exact parallel because a Lisp block can return a value, while that concept doesn’t apply in C++.

In Lisp, a block that exits normally returns the result of its last statement.  In the event of a branch with return-from, an optional second argument is the return value of the block, otherwise the block returns nil.

Note that the Lisp return statement does not take a name as an argument, and returns from the innermost enclosing unnamed (nil) block, whether named or not.  This might be a block you created with the block directive, or it might be an implicit block surrounding one of the looping constructs, so for safety you’re encouraged to use named blocks and return-from when that is appropriate.  When using a looping construct like do, dolist, or dotimes, a bare return reads like a break in C++.