Search code examples
typescommon-lispgeneric-function

Common Lisp: Generic Function Specializing on Array Length


I am just getting started with generic functions and am wondering if this is possible (I really hope so!).

I have made 3 packages for handling vectors of different lengths: vector2, vector3 and vector4.

Each package has functions that handle vectors of that length:

vector2:normalize - for normalizing *vector2s*
vector3:normalize - for normalizing *vector3s*
etc.

My vectors are typed arrays (for speed and memory use as this is for writing games) so a vector3 is:

(make-array 3 :element-type `single-float).

Now I am writing a package called vectors which will contain generic functions to handle any vector types.

So passing vector:normalize a vector3 should return a vector3 and so on.

I tried this:

(defmethod v+1 ((vec-a #.(class-of (make-array 3 
                       :element-type 
                       `single-float)))
        (vec-b #.(class-of (make-array 3 
                       :element-type 
                       `single-float))))
  (v3:v+1 vec-a vec-b))



(defmethod v+1 ((vec-a #.(class-of (make-array 4
                       :element-type 
                       `single-float)))
        (vec-b #.(class-of (make-array 4 
                       :element-type 
                       `single-float))))
  (v4:v+1 vec-a vec-b))

...based on what I saw in question 6083238, but obviously that only specialized on simple, single-float arrays as:

V> (class-of (make-array 4 :element-type  `single-float))
#<BUILT-IN-CLASS SB-KERNEL::SIMPLE-ARRAY-SINGLE-FLOAT>

What would be the best method of doing this, considering it needs to be fast and not memory hogging?

Cheers in advance!


Solution

  • Generic functions in CL can be specialized either with classes or EQL-specializer (see PCL chapter on GFs). Classes aren't types, although there's some relation. But in your case you have a single class and a single type. So, effectively, you want to specialize the methods on some arbitrary property. This can only be achieved with an EQL-specializer:

    (defmethod v+1 ((size (eql 3)) vec-a vec-b)
      (v3:v+1 vec-a vec-b))
    (defmethod v+1 ((size (eql 4)) vec-a vec-b) 
      (v4:v+1 vec-a vec-b))
    

    They don't do any bounds checking, and also are somewhat more clumsy. The first problem can be solved by adding a check inside the method's body:

    (defmethod v+1 ((size (eql 3)) vec-a vec-b)
      (assert (= 3 (length vec-a) (length vec-b))
        "Vector size mismtach")
      (v3:v+1 vec-a vec-b))
    

    You can also define a macro to generate such methods for any size.

    Another option is to use a macro at call site, that will present a simpler interface and can perform error checking as well:

    (defmacro v+1* (vec-a vec-b)
      (once-only (vec-a vec-b)
        `(if (= (length ,vec-a) (length ,vec-b))
             (v+1 (length ,vec-a) ,vec-a ,vec-b)
             (error "Vector size mismatch"))))
    

    For a discussion of ONCE-ONLY see PCL chapter on macros.