Common Lisp:专门研究数组长度的通用函数

时间:2012-08-16 22:30:42

标签: types common-lisp generic-function

我刚刚开始使用泛型函数,我想知道这是否可行(我真的希望如此!)。

我已经制作了3个用于处理不同长度矢量的包: vector2,vector3 vector4

每个包都有处理该长度向量的函数:

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

我的向量是类型化数组(用于速度和内存使用,因为这是用于编写游戏)所以 vector3 是:

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

现在我正在编写一个名为 vectors 的包,它将包含处理任何矢量类型的通用函数。

所以传递向量:规范化 vector3 应该返回 vector3 ,依此类推。

我试过了:

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

...基于我在question 6083238中看到的内容,但显然只专注于简单的单浮点数组:

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

这样做的最佳方法是什么,考虑到它需要快速而不是占用内存?

提前干杯!

2 个答案:

答案 0 :(得分:3)

CL中的通用函数可以专门用于类或EQL专用器(参见PCL chapter on GFs)。虽然有一些关系,但类不是类型。但在您的情况下,您只有一个类和一个类型。因此,实际上,您希望在某些任意属性上专门化方法。这只能通过EQL专用器来实现:

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

他们没有做任何边界检查,也有点笨拙。第一个问题可以通过在方法体内添加一个检查来解决:

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

您还可以定义一个宏来为任何大小生成此类方法。

另一种选择是在呼叫站点使用宏,它将提供更简单的接口,并且还可以执行错误检查:

(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"))))

有关ONCE-ONLY的讨论,请参阅PCL chapter on macros

答案 1 :(得分:1)

基本上,没有办法根据矢量的大小进行调度。正如Vsevolod指出的那样,CLOS中的泛型函数基于类进行调度,而Common Lisp中的数组类不会被它所拥有的元素数量所改变。

然而,如果表现是你的主要目标,那么它可能不是你想要做的任何一种方式;涉及在如此低水平的每次操作中进行多次调度可能会使您陷入困境。

可能的替代方案:

  1. 像工程师一样思考。说服自己,2号,3号和4号矢量的运算符对于你的目的来说根本是不同的东西,并且可能出现在这样的不同情况下,对每个符号都有一个符号表示法是有意义的,然后尽可能地调整这些函数。例如:只需定义并使用+ vector3,normalize-vector3等。

  2. 像数学家一样思考。定义尽可能最通用的运算符,它们应该适用于任何向量长度。稍后担心性能,仅优化代码的特定部分,这些部分在实际运行的程序中降低了最多。例如:

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

  3. 像人们一样认为Common Lisp程序员会思考并将其全部消除。如果你想要效率,但觉得你需要一个一致的界面,那么如果泛型函数不能做你想要的,或者不能足够快,你可以尝试这样的事情:

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

    这将允许您定义标准向量接口(v + 1,规范化等)中的名称与用于执行相关操作的任何专用函数的名称之间的映射(在建议1中命名,或者包 - 合格)。例如:

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

    导致

    等形式
    #2v(normalize (v+1 a b)) ; => (normalize-vector2 (+vector2 a b))
    #3v(normalize (v+1 a b)) ; => (normalize-vector3 (+vector3 a b))
    

    使用专门的ops作为表单进行读取,让您可以为任何向量维度定义此类映射,如果您希望任何代码片段适用于不同的向量维度,则只更改#v中的数字。

    (如果您使用标准命名约定,您可以让DEFVECTOROPS为您定义这些映射,但有时最好保持明确的内容)。

  4. 请记住,上面的任何代码都是未经测试的(我正在工作且没有可用的lisp系统),最后的解决方案尤其充满了Starship Troopers级别的错误,会严重伤害你(有更好的方法,但这只是为了说明),但我只是认为考虑一些可能的替代解决方案可能是好的。您选择哪个取决于程序/程序员的最佳选择。 (不过,我可能会选择选项1或2。)