Question

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 :)

Was it helpful?

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.)

OTHER TIPS

Note that the element-type to a MAKE-ARRAY is not a real type declaration. It's a hint to the Lisp implementation what kind of data the array should be able to store. It then might select specialized array implementations or not.

UPGRADED-ARRAY-ELEMENT-TYPE returns the element type of the most specialized array representation capable of holding items of the type denoted by typespec.

CL-USER 12 > (upgraded-array-element-type '(integer 0 100))
(UNSIGNED-BYTE 8)

Above means that I request an array with integer elements between 0 and 100. This Lisp (here LispWorks) will give me an array with element type (unsigned-byte 8).

More examples:

CL-USER 13 > (upgraded-array-element-type 'fixnum)
(SIGNED-BYTE 64)

CL-USER 14 > (upgraded-array-element-type 'complex)
T

CL-USER 15 > (defclass foo-component () ())
#<STANDARD-CLASS FOO-COMPONENT 402030196B>

CL-USER 16 > (upgraded-array-element-type 'foo-component)
T

T here means that the array actually will store all kinds of data objects.

CL-USER 17 > (upgraded-array-element-type '(or null foo-component))
T

CL-USER 20 > (make-array 2
                         :element-type 'foo-component
                         :initial-element (make-instance 'foo-component))
#(#<FOO-COMPONENT 40203B9A03> #<FOO-COMPONENT 40203B9A03>)

CL-USER 21 > (array-element-type *)
T

Above shows that Lisp also then forgets, what was requested initially. We actually got an array of element-type T and when we ask for its element-type, then it is T.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top