Search code examples
lispcommon-lispslotclos

Why can CLOS slots be unbound?


It is said, only special variables in Common Lisp can be unbound. For all lexical variables the default value is nil. I thought that class slots exist in something like closure, but obviously they don't.

If I define a CLOS slots without :initform parameter (in hope that they will be bound to nil anyway) and do not supply values when creating instance, I'll get an instance with unbound slots. Why it is so? It's not handy.


Solution

  • CLOS instances are not closures

    CLOS instances are usually not implemented as closures. That would be difficult. They are some data structure with something like a vector for the slots. Similar to Common Lisp's structures. A difference between CLOS instances and structures complicates it: it is possible for CLOS instances to change the number of slots at runtime and one can change the class of a CLOS instance at runtime.

    Making sure slots have a NIL value

    With some advanced CLOS you can make sure that slots have a NIL value. Note that the functions in the package CLOS in my example may be in another package in your CL.

    This functions looks over all slots of an instance. If a slot is unbound, it gets set to NIL.

    (defun set-all-unbound-slots (instance &optional (value nil))
      (let ((class (class-of instance)))
        (clos:finalize-inheritance class)
        (loop for slot in (clos:class-slots class)
              for name = (clos:slot-definition-name slot)
              unless (slot-boundp instance name)
              do (setf (slot-value instance name) value))
        instance))
    

    We make a mixin class:

    (defclass set-unbound-slots-mixin () ())
    

    The fix will be run after initializing the object.

    (defmethod initialize-instance :after ((i set-unbound-slots-mixin) &rest initargs)
      (set-all-unbound-slots i nil))
    

    Example:

    (defclass c1 (set-unbound-slots-mixin)
      ((a :initform 'something)
       b
       c))
    
    
    CL-USER 1 > (describe (make-instance 'c1))
    
    #<C1 4020092AEB> is a C1
    A      SOMETHING
    B      NIL
    C      NIL