Now we come across another unusual feature of Lisp, the standard generic function reinitialize-instance. This function is used to modify the local slots (in C++ terms, the non-static members) of an instance. Now, its default behaviour isn’t especially interesting, as it behaves essentially like access through multiple calls to slot-value. However, it can be specialized by the programmer, allowing him/her to take additional actions based on the initialization. For instance, here is how one might code up a particular behaviour without reinitialize-instance:
(defclass citizen () ((age :initform 0 :initarg :age :accessor get-age) (minor :initform t :initarg :minor :accessor get-minor) (voter :initform nil :initarg :voter :accessor get-voter) (can-drink :initform nil :initarg :drinker :accessor get-drinker))) (defun year-goes-by (person) (case (incf (get-age person)) (18 (setf (get-minor person) nil (get-voter person) t)) (21 (setf (get-drinker person) t)))) (defun demonstrate () (let ((person (make-instance 'citizen))) (dotimes (i 30) (format t "Age: ~D Minor: ~A Voter: ~A Drinker: ~A~%" (get-age person) (get-minor person) (get-voter person) (get-drinker person)) (year-goes-by person))))
Now, we write a new version with reinitialize-instance. Note that the age-dependent rules are now bound in a method specialized on the class, not in an external function. By replacing the accessors with readers, we force users to update the instance with reinitialize-instance (they can also use slot-value, which defeats this code, but we document the requirement that slot-value not be used):
(defclass citizen () ((age :initform 0 :reader get-age :initarg :age) (minor :initform t :reader get-minor :initarg :minor) (voter :initform nil :reader get-voter :initarg :voter) (can-drink :initform nil :reader get-drinker :initarg :drinker)) (:documentation "Never alter slots with slot-value, use reinitialize-instance with :age instead")) (defmethod reinitialize-instance ((person citizen) &rest initargs) (let ((new-age (second (member :AGE initargs)))) (when new-age (setf (slot-value person 'age) new-age) (cond ((< new-age 18) (setf (slot-value person 'minor) t (slot-value person 'voter) nil (slot-value person 'can-drink) nil)) ((< new-age 21) (setf (slot-value person 'minor) nil (slot-value person 'voter) t (slot-value person 'can-drink) nil)) (t (setf (slot-value person 'minor) nil (slot-value person 'voter) t (slot-value person 'can-drink) t))))) person) (defun demonstrate () (let ((person (make-instance 'citizen))) (dotimes (i 30) (format t "Age: ~D Minor: ~A Voter: ~A Drinker: ~A~%" (get-age person) (get-minor person) (get-voter person) (get-drinker person)) (reinitialize-instance person :age i))))
Finally, I’ll note that a common way to specialize reinitialize-instance is to write an :after method for it, not to replace the default function. This allows the programmer to let the system apply its normal rules for replacing slot values, then the specialized :after method can swoop in and tweak slots as necessary.