我刚刚开始使用泛型函数,我想知道这是否可行(我真的希望如此!)。
我已经制作了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>
这样做的最佳方法是什么,考虑到它需要快速而不是占用内存?
提前干杯!
答案 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中的数组类不会被它所拥有的元素数量所改变。
然而,如果表现是你的主要目标,那么它可能不是你想要做的任何一种方式;涉及在如此低水平的每次操作中进行多次调度可能会使您陷入困境。
可能的替代方案:
像工程师一样思考。说服自己,2号,3号和4号矢量的运算符对于你的目的来说根本是不同的东西,并且可能出现在这样的不同情况下,对每个符号都有一个符号表示法是有意义的,然后尽可能地调整这些函数。例如:只需定义并使用+ vector3,normalize-vector3等。
像数学家一样思考。定义尽可能最通用的运算符,它们应该适用于任何向量长度。稍后担心性能,仅优化代码的特定部分,这些部分在实际运行的程序中降低了最多。例如:
(defun v+ (&rest vectors)
(apply #'map 'vector #'+ vectors))
(defun normalize (vector)
(sqrt (reduce (lambda (acc x) (+ acc (* x x))) vector
:initial-value 0)))
等
像人们一样认为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为您定义这些映射,但有时最好保持明确的内容)。
请记住,上面的任何代码都是未经测试的(我正在工作且没有可用的lisp系统),最后的解决方案尤其充满了Starship Troopers级别的错误,会严重伤害你(有更好的方法,但这只是为了说明),但我只是认为考虑一些可能的替代解决方案可能是好的。您选择哪个取决于程序/程序员的最佳选择。 (不过,我可能会选择选项1或2。)