2012-08-16 23 views
1

我剛開始使用泛型函數,想知道這是否可能(我真的希望如此!)。Common Lisp:專用於數組長度的泛型函數

我已用於處理不同長度的向量3包:vector2,的Vector3的Vector4

每個包具有處理該長度的矢量的功能:

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

我的向量類型數組(對速度和內存使用,因爲這是用於寫入遊戲)這樣一個的Vector3是:

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

現在我正在編寫一個名爲的矢量包,它將包含處理任何矢量類型的泛型函數。

所以傳遞載體:一個正常化的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> 

會是什麼考慮到它需要快速而不是內存佔用,這是做這件事的最好方法嗎?

乾杯提前!

回答

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

+0

絕對鑽石!很好的解釋,鏈接和示例代碼。像這樣的答案是爲什麼stackoverflow是美好的。今晚我會實施這個! – Baggers 2012-08-17 10:04:51

+0

很高興聽到這一點。你成功實施了這個嗎? – 2012-08-18 06:17:17

+0

我當然做到了!我還沒有回去讓宏產生defmethods,但它的好處是能夠在repl中敲出想法而不用擔心向量長度。希望很快我會有一些很好的3D演示來展示,但即使在這個階段,也能夠玩弄代碼並看到正在運行的opengl演示變化是可愛的(對於這種交互式編譯來說,我相當陌生!) 。再次感謝 – Baggers 2012-08-20 15:38:58

1

本質上,沒有辦法根據矢量的大小進行分派。正如Vsevolod所指出的那樣,基於類的CLOS調度中的泛型函數以及Common Lisp中的數組的類不會被它所保存的元素數量所改變。然而,如果表現是你的主要目標,那麼它可能不是你想要做的任何事情;涉及在如此低水平的每次操作中進行多次調度可能會使您稍微陷入困境。

可能的替代:

  1. 這樣想的工程師。相信2,3和4矢量的運營商對於您的目的而言是基本上不同的東西,並且可能出現在這樣的不同環境中,因此對每個矢量都有一個distict符號是有意義的,然後儘可能多地調整這些函數。例如:只需定義並使用+ 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,normalize等)與用於執行關聯操作的任何專用函數的名稱之間的映射(或者按照建議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定義這些映射,但有時最好讓事情明確)。

千萬牢記上面的任何代碼是未經測試(我在工作和沒有可用口齒不清系統),最後的解決方案尤其是滿的,這將嚴重傷害星河戰隊級別的bug你(有更好的方法去做,但這只是爲了說明),但我認爲考慮一些可能的替代解決方案可能是好的。你選擇哪個取決於最適合程序/程序員。 (儘管我可能會選擇1或2)。

+0

感謝您的優秀破敗!對於核心,我絕對堅持'工程師'方法,並且真正有'數學家'方法可以使想法真正無痛化。我不得不說我喜歡女巫工藝(#3)部分。我將自己限制在一個閱讀器宏中,以使創建矢量變得更清潔,但是在我深入挖掘這個包之前,我需要完成更多的'放棄lambda'章節。儘管如此,爲了達到速度目的而使用宏可能會讓我感到非常興奮。再次感謝 – Baggers 2012-08-20 15:44:13