15

+++2 つの数学ベクトルを加算する関数が必要です。

ベクトルを次のように実装し[x, y, z]て使用できます。

(+++) :: (Num a) => [a] -> [a] -> [a]
(+++) = zipWith (+)

したがって、任意のn次元ベクトルに対応します (したがって、これも機能し[x, y]ます)。

または、ベクトルを次のように実装し(x, y, z)て使用することもできます。

type Triple a = (a, a, a)

merge :: (a -> b -> c) -> Triple a -> Triple b -> Triple c
merge f (a, b, c) (x, y, z) = (f a x, f b y, f c z)

(+++) :: (Num a) => Triple a -> Triple a -> Triple a
(+++) = merge (+)

もちろん、これはもう少し複雑ですが、他のすべてのベクトル関数を実装すると、それは無関係です (40 行ではなく 50 行)。

リスト アプローチの問題は、2D ベクトルを 3D ベクトルに追加できることです。その場合、zipWith単純に 3D ベクトルのzコンポーネントを切り取ります。それは理にかなっているかもしれませんが(2Dベクトルを に拡張する必要がある可能性が高い[x, y, 0]です)、他の機能については、どちらかが静かに発生すると問題になる可能性があると考えています。タプル アプローチの問題は、ベクトルが 3 つのコンポーネントに制限されることです。

直感的には、ベクトルを として表現する方が理にかなっていると思います。これは(x, y, z)、数学的ベクトルには固定数のコンポーネントがあり、コンポーネントをベクトルにコンス (先頭に追加) することは実際には意味がないためです。

一方で、3D ベクトル以外のものが必要になる可能性はほとんどありませんが、それに限定するのは適切ではないようです。

私が欲しいのは、同じ長さの 2 つのリストを取る関数か、任意のサイズのタプルを操作する関数だと思います。

実用性、スケーラビリティ、エレガンスなどの点で何か提案はありますか?

4

4 に答える 4

21

型レベルのプログラミングを使用できます。まず、すべての自然数を別の型にする必要があります。ペアノの自然数の定義に従って、Zis 0、およびS xisx + 1

data Z = Z
data S a = S a

class Nat a
instance Nat Z
instance (Nat a) => Nat (S a)

これで、タイプVecを使用してリストを単純にラップできますが、 を使用してそのサイズを追跡できNatます。このために、スマート コンストラクター nilを使用します (そのため、モジュールから<:>データ コンストラクターをエクスポートしないでください)。Vec

data Vec a = Vec a [Int]

nil = Vec Z []

infixr 5 <:>
x <:> (Vec n xs) = Vec (S n) (x:xs)

addこれで、2 つのベクトルが同じ を持つ必要がある関数を定義できますNat

add :: Nat a => Vec a -> Vec a -> Vec a
add (Vec n xs) (Vec _ ys) = Vec n (zipWith (+) xs ys) 

これで、長さ情報を持つベクター型ができました。

toList (Vec _ xs) = xs
main = print $ toList $ add (3 <:> 4 <:> 2 <:> nil) (10 <:> 12 <:> 0 <:> nil) 

もちろん、ここで異なる長さのベクトルを使用すると、コンパイル エラーが発生します。

これは理解しやすいバージョンです。より短く、より効率的で、より便利なソリューションがあります。

于 2012-05-16T22:19:11.353 に答える
14

最も簡単な方法は、+++演算子を型クラスに入れ、さまざまなタプルサイズのインスタンスを作成することです。

{-# LANGUAGE FlexibleInstances #-}   -- needed to make tuples type class instances

class Additive v where
  (+++) :: v -> v -> v

instance (Num a) => Additive (a,a) where
  (x,y) +++ (ξ,υ)  =  (x+ξ, y+υ)
instance (Num a) => Additive (a,a,a) where
  (x,y,z) +++ (ξ,υ,ζ)  =  (x+ξ, y+υ, z+ζ)
...

このようにして、可変長のタプルを追加できますが、コンパイル時に、両側が常に同じ長さになることが保証されます。


これを一般化して、実際の型クラスでのような関数を使用することmergeもできます。この場合、クラスインスタンスを型コンストラクター(リストモナドなど)として指定する必要があります。

class Mergable q where
  merge :: (a->b->c) -> q a -> q b -> q c

instance Mergable Triple where
  merge f (x,y,z) (ξ,υ,ζ) = (f x ξ, f y υ, f z ζ)

そして単に

(+++) :: (Mergable q, Num a) => q a -> q b -> q c
+++ = merge (+)

残念ながら、型の同義語が部分的に評価されない可能性があるため、これは完全には機能しません。Triple代わりに、次のようなニュータイプを作成する必要があります

newtype Triple a = Triple(a,a,a)

その後

instance Mergable Triple where
  merge f (Triple(x,y,z)) (Triple((ξ,υ,ζ)) = Triple(f x ξ, f y υ, f z ζ)

もちろん、これは見た目ほど良くありません。

于 2012-05-16T21:58:20.283 に答える
1

OP はより軽量なアプローチを望んでいたため、関連付けられた型を使用します。

class VecMath a b where
    type Res a b :: *
    (+++) :: a -> b -> Res a b

instance Num a => VecMath (a,a,a) (a,a,a) where
    type Res (a,a,a) (a,a,a) = (a,a,a)
    (x1,y1,z1) +++ (x2,y2,z2) = (x1+x2, y1+y2, z1+z2)

instance Num a => VecMath (a,a) (a,a,a) where
    type Res (a,a) (a,a,a) = (a,a,a)
    (x1,y1) +++ (x2,y2,z) = (x1+x2, y1+y2, z)

instance Num a => VecMath (a,a,a) (a,a) where
    type Res (a,a) (a,a,a) = (a,a,a)
    -- (+++) analog
instance Num a => VecMath (a,a) (a,a) where
    type Res (a,a) (a,a) = (a,a)
    -- ...

Resは型関数であり、ここでは本質的にその引数の「より大きな」型になります。利点は、あたかも存在しないかのように、単純な古いタプルを使用できることですVecMath。.のドメインに新しい型を追加することを検討する場合、作成する必要があるインスタンスが指数関数的に爆発するという暗黒面がありますRes。詳細については、こちらを参照してください。

于 2012-05-24T14:07:00.837 に答える
-1

Landei さんと leftaroundabout さんの回答は良いものでした (お二人に感謝します)。私が提案したオプションのいずれかを実行しようとすると、複雑なコードが作成されますが、それ自体は問題にはなりませんが、ユーザー コードも見栄えがよくないように思われます。

リストを使用するよりも意味的に正しいように見えるという理由だけで、タプルを使用して 3 次元のみのベクトルに固執することにしたと思います。mapただし、トリプルの、zipWithsumおよびその他を再実装することになります。私はシンプルさを保ちたいと思っています — ベクトルをリストと考える説得力のある議論があれば、その解決策はよりうまく機能するように感じます (次元を混ぜないように注意すれば)… 実際にベクトルを使用すると、ただし、関数は可変次元のベクトルではなく、3d ベクトルを引数として取り、それNum a => [a]を強制することはできません。

于 2012-05-16T22:53:20.743 に答える