1

私はジェネリック関数を使い始めたばかりで、これが可能かどうか疑問に思っています (私は本当にそう願っています!)。

長さの異なるベクトルを処理するための 3 つのパッケージを作成しました: vector2、vector3、およびvector4

各パッケージには、その長さのベクトルを処理する関数があります。

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

私のベクトルは型付き配列です(ゲームを書くための速度とメモリの使用のため)ので、vector3は次のとおりです。

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

現在、任意のベクター型を処理するための汎用関数を含む、vectorsというパッケージを作成しています。

したがって、vector:normalize を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 スペシャライザーのいずれかで特化できます ( GF に関する PCL の章を参照)。いくつかの関係はありますが、クラスは型ではありません。しかし、あなたの場合、単一のクラスと単一のタイプがあります。したがって、事実上、メソッドを任意のプロパティに特化する必要があります。これは、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"))))

の議論については、マクロに関する PCL の章ONCE-ONLYを参照してください。

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

    特殊な ops を使用してフォームとして読み取るには、ベクトルの任意の次元に対してそのようなマッピングを定義できるようにし、コードの一部をベクトルの異なる次元で機能させたい場合は #v の数字のみを変更します。

    (標準の命名規則を使用する場合は、DEFVECTOROPS にこれらのマッピングを定義させることもできますが、明示的に行うことが最善の場合もあります)。

上記のコードはすべてテストされていないことを覚えておいてください (私は仕事をしていて、利用可能な Lisp システムを持っていません)、特に最後の解決策は、あなたを深刻に傷つける Starship Troopers レベルのバグでいっぱいです (もっとまともな方法があります)。ただし、これは単なる説明のためのものです)。どちらを選択するかは、プログラム/プログラマーに最適かどうかによって異なります。(ただし、オプション1または2を選択する可能性があります。)

于 2012-08-20T14:00:09.307 に答える