Common Lisp: a general function specializing in array length

I'm just starting out with common features, and I wonder if this is possible (I really hope so!).

I created 3 packages for processing vectors of different lengths: vector2, vector3 and vector4.

Each package has functions that process vectors of this length:

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

My vectors are typed arrays (for speed and memory usage, as is done for writing games), so vector3:

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

Now I am writing a package called vectors, which will contain common functions for processing any types of vectors.

So, the passing vector: normalize vector3 should return vector3, etc.

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 it is obvious that he specializes only in simple arrays with one float like:

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

What would be the best way to do this, given that it should be fast, not memory?

Welcome in advance!

+1
source share
2 answers

Common functions in CL can be specialized with either classes or the EQL-specializer (see the PCL chapter on GF ). Classes are not types, although there is some connection. But in your case, you have one class and one type. So, effectively, you want to specialize methods on some arbitrary property. This can only be achieved with the 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 do not check boundaries, and are also somewhat more awkward. The first problem can be solved by adding a check inside the method 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 create such methods for any size.

Another option is to use a macro on the call site, which will present a simpler interface and can also perform error checking:

 (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 the PCL Macro chapter .

+3
source

Essentially, there is no way to send to fit the vector. As Vsevolod points out, the common functions in the CLOS-based distribution are based on the class, and the array class in Common Lisp does not change by the number of elements it has.

However, if productivity is your main goal, then this may not be what you would like to do anyway; including multiple scheduling in each operation at such a low level can potentially get you a little bogged down.

Possible alternatives:

  • Think like an engineer. Convince yourself that the operators on 2-, 3- and 4-vectors are fundamentally different things for your purposes and are likely to appear in such different circumstances that it makes sense to have a symbol for each and then configure these functions as much as possible. For example: just define and use + vector3, normalize-vector3, etc.

  • Think like a mathematician. Identify the most common possible operators that should work on any length of the vector. Worry about performance later by optimizing only those specific pieces of code that slow you down in the most running program. For instance:

     (defun v+ (&rest vectors) (apply #'map 'vector #'+ vectors)) (defun normalize (vector) (sqrt (reduce (lambda (acc x) (+ acc (* xx))) vector :initial-value 0))) 

    and etc.

  • Think about how people think. General Lisp programmers think, and the macro of everything is away. If you want to increase efficiency, but feel that you need a serial interface, then if common functions cannot do what you want, or cannot do it fast enough, you can try something like this:

     (defvar *vector-op-table* '()) (defmacro defvectorops (dimensions &body mappings) `(setf (getf *vector-op-table* ,dimensions) ',mappings)) (defun vector-op-reader (stream subchar numarg) (declare (ignore subchar)) (let ((form (read stream)) (op-table (getf *vector-op-table* numarg))) (sublis op-table form))) (set-dispatch-macro-character #\# #\v #'vector-op-reader) 

    So that you can determine the mappings between the names in the standard vector interface (v + 1, normalize, etc.) and the names of any specialized functions for performing related operations (or named as in Assumption 1, qualified). For instance:

     (defvectorops 2 (v+1 . +vector2) ; or vector2::v+1, if you want (normalize . normalize-vector2) ...) (defvectorops 3 (v+1 . +vector3) ...) 

    What causes forms like

     #2v(normalize (v+1 ab)) ; => (normalize-vector2 (+vector2 ab)) #3v(normalize (v+1 ab)) ; => (normalize-vector3 (+vector3 ab)) 

    to read as a form using specialized operating systems, allowing you to define such mappings for any dimension of the vector, changing only the number in #v if you want any piece of code to work for different dimensions of the vector.

    (You could DEFVECTOROPS define these mappings for you if you use the standard naming convention, but sometimes it is best to keep things explicit). A.

Keep in mind that any of the above code has not been verified (I am at work and do not have a Lisp system available), and the latter solution is especially full of Starship Troopers level errors that can seriously harm you (there are more robust ways to do this, but it was just for illustrations), but I just thought it would be nice to consider some possible alternative solutions. The choice you choose depends on the best fit for the program / programmer. (I would probably go for option 1 or 2, though.)

+1
source

Source: https://habr.com/ru/post/955115/


All Articles