1

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

我制作了 3 个包来处理不同长度的向量:vector2、vector3vector4

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

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

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

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

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

所以传递 vector:normalize a 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))

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

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

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

提前干杯!

4

2 回答 2

3

CL 中的泛型函数可以用类或 EQL-specializer 来专门化(参见关于 GFs 的 PCL 章节)。类不是类型,尽管有一些关系。但是在您的情况下,您只有一个类和一个类型。因此,实际上,您希望将方法专门用于某些任意属性。这只能通过 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))

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

(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 章节

于 2012-08-17T07:24:41.517 回答
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))
    

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

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

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

于 2012-08-20T14:00:09.307 回答