Skip to main content

change-class

change-class Standard Generic Function

Syntax:

change-class instance new-class &key &allow-other-keys → instance

Method Signatures:

change-class (instance standard-object) (new-class standard-class) &rest initargs

change-class (instance t) (new-class symbol) &rest initargs

Arguments and Values:

instance—an object.

new-class—a class designator .

initargs—an initialization argument list.

Description:

The generic function change-class changes the class of an instance to new-class. It destructively modifies and returns the instance.

change-class

If in the old class there is any slot of the same name as a local slot in the new-class, the value of that slot is retained. This means that if the slot has a value, the value returned by slot-value after change-class is invoked is eql to the value returned by slot-value before change-class is invoked. Similarly, if the slot was unbound, it remains unbound. The other slots are initialized as described in Section 7.2 (Changing the Class of an Instance).

After completing all other actions, change-class invokes update-instance-for-different-class. The generic function update-instance-for-different-class can be used to assign values to slots in the transformed instance. See Section 7.2.2 (Initializing Newly Added Local Slots).

If the second of the above methods is selected, that method invokes change-class on instance, (find-class new-class), and the initargs.

Examples:

(defclass position () ()) 
(defclass x-y-position (position)
((x :initform 0 :initarg :x)
(y :initform 0 :initarg :y)))
(defclass rho-theta-position (position)
((rho :initform 0)
(theta :initform 0)))
(defmethod update-instance-for-different-class :before ((old x-y-position)
(new rho-theta-position)
&key)
;; Copy the position information from old to new to make new
;; be a rho-theta-position at the same position as old.
(let ((x (slot-value old ’x))
(y (slot-value old ’y)))
(setf (slot-value new ’rho) (sqrt (+ (\* x x) (\* y y)))
(slot-value new ’theta) (atan y x))))
;;; At this point an instance of the class x-y-position can be
;;; changed to be an instance of the class rho-theta-position using
;;; change-class:
(setq p1 (make-instance ’x-y-position :x 2 :y 0))
(change-class p1 ’rho-theta-position)
;;; The result is that the instance bound to p1 is now an instance of
;;; the class rho-theta-position. The update-instance-for-different-class
;;; method performed the initialization of the rho and theta slots based

;;; on the value of the x and y slots, which were maintained by
;;; the old instance.

See Also:

update-instance-for-different-class, Section 7.2 (Changing the Class of an Instance)

Notes:

The generic function change-class has several semantic difficulties. First, it performs a destructive operation that can be invoked within a method on an instance that was used to select that method. When multiple methods are involved because methods are being combined, the methods currently

executing or about to be executed may no longer be applicable. Second, some implementations might use compiler optimizations of slot access, and when the class of an instance is changed the assumptions the compiler made might be violated. This implies that a programmer must not use change-class inside a method if any methods for that generic function access any slots, or the results are undefined.

Expanded Reference: change-class

Basic Class Change

change-class destructively changes the class of an instance. Slots that exist in both the old and new class retain their values. Slots only in the new class are initialized according to their initforms or remain unbound.

(defclass employee ()
((name :initarg :name :accessor person-name)
(company :initarg :company :accessor employee-company)))

(defclass retiree ()
((name :initarg :name :accessor person-name)
(pension :initarg :pension :accessor retiree-pension :initform 0)))

(let ((e (make-instance 'employee :name "Alice" :company "Acme")))
(change-class e 'retiree)
(list (person-name e)
(class-name (class-of e))
(retiree-pension e)))
=> ("Alice" RETIREE 0)

Slots Are Retained or Dropped

Slots with matching names between the old and new class keep their values. Slots from the old class that are not in the new class are dropped. Slots in the new class that are not in the old class get initialized.

(defclass point-2d ()
((x :initarg :x :accessor point-x)
(y :initarg :y :accessor point-y)))

(defclass point-3d ()
((x :initarg :x :accessor point-x)
(y :initarg :y :accessor point-y)
(z :initarg :z :accessor point-z :initform 0)))

(let ((p (make-instance 'point-2d :x 1 :y 2)))
(change-class p 'point-3d)
(list (point-x p) (point-y p) (point-z p)))
=> (1 2 0)

Using Initargs During change-class

You can pass keyword arguments to change-class to initialize the new slots.

(defclass point-2d ()
((x :initarg :x :accessor point-x)
(y :initarg :y :accessor point-y)))

(defclass point-3d ()
((x :initarg :x :accessor point-x)
(y :initarg :y :accessor point-y)
(z :initarg :z :accessor point-z :initform 0)))

(let ((p (make-instance 'point-2d :x 5 :y 10)))
(change-class p 'point-3d :z 15)
(list (point-x p) (point-y p) (point-z p)))
=> (5 10 15)

Customizing with update-instance-for-different-class

The update-instance-for-different-class generic function is called during change-class to allow custom initialization of the transformed instance.

(defclass cartesian ()
((x :initarg :x :accessor coord-x)
(y :initarg :y :accessor coord-y)))

(defclass polar ()
((rho :accessor coord-rho)
(theta :accessor coord-theta)))

(defmethod update-instance-for-different-class
:before ((old cartesian) (new polar) &key)
(let ((x (coord-x old))
(y (coord-y old)))
(setf (coord-rho new) (sqrt (+ (* x x) (* y y))))
(setf (coord-theta new) (atan y x))))

(let ((c (make-instance 'cartesian :x 3.0 :y 4.0)))
(change-class c 'polar)
(list (coord-rho c) (coord-theta c)))
=> (5.0 0.9272952)