Search code examples
typeslispcommon-lispclos

Treat nil as wildcard type


I'm writing a Lisp program and am trying to be a bit conscientious about types. I guess there's performance improvements, but I'm more interested in using type annotations for documentation and safety. The problem is nil. I've run into two problems so far.

Exhibit A:

>(defmethod foo ((bar bar-class) (quux quux-class))
   ...)

>(foo (make-instance 'bar-class) nil)
 ERROR: No applicable method, etcetera etcetera, because nil is not of type quux-class

Exhibit B:

(defmethod initialize-instance :after ((f foo) &rest args)
  "Initialize the grid to be the right size, based on the height and width of the foo."
  (declare (ignorable args))
  (setf (slot-value f 'grid) (make-array (list (width f) (height f))
                                         :element-type 'foo-component
                                         :adjustable nil
                                         :initial-element nil)))

style-warning: 
  NIL is not a FOO-COMPONENT.

What's the best practice here? So far the only remotely-insightful idea I've had is to use the null object pattern and have (defclass nil-quux-class (quux-class) ...) and (defclass nil-foo-component (foo-component) ...), but that seems hacky at best. I'm not sure why, but it does. Frankly I'm not used to design patterny workarounds in CLOS :)


Solution

  • (A) What to you want to happen when you call foo with nil for the quux argument?

    If you want nothing at all to happen then

    (defmethod foo ((bar bar-class) (quux null))
      nil)
    

    will sort you out.

    If you want the same code to be called as if you had passed an instance of quux-class, then either:

    (defmethod foo ((bar bar-class) (quux quux-class))
      (do-stuff bar quux))
    
    (defmethod foo ((bar bar-class) (quux null))
      (do-stuff bar quux))
    

    or:

    (defmethod foo ((bar bar-class) quux)
      (unless (or (typep bar 'bar-class)
                  (null bar))
        (error "Expected a bar-class or null, got ~s." quux))
      (do-stuff bar quux))
    

    (B) You've gone

    (make-array size :element-type 'foo-component
                     :initial-element nil)
    

    and your lisp implementation has pointed out a contradiction - the initial elements can't be both nil and foo-components. (Well, I guess that depends on what your type foo-component looks like. I'm assuming it doesn't include null.)

    You might consider:

    (make-array :element-type '(or foo-component null)
                :initial-element nil)
    

    but be aware: what do you want your lisp to gain from knowing that an array will contain either foo-components or nils? Optimisation? Error checking on your behalf? (Your mileage might vary, according to which lisp implementation you're using.)