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

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

反垃圾邮件 / Anti-spam question * Time limit is exhausted. Please reload CAPTCHA.