2

在 Haskell 中,如果您有一个“族”类型(例如,N 乘 N 元素矩阵,对于 N 的某些值),以及一个并行的“相关”类型族(例如,N 元素向量,对于N 的相同值),以及需要每个族中的一种特定类型的操作(例如,将 N 乘 N 元素矩阵和 N 元素列向量相乘),是否可以为该类型声明类型类手术?

对于这个具体的例子,我想它看起来像这样:

class MatrixNxN m where

  --| Multiplication of two N-by-N-element matrices
  mmul :: Num a => m a -> m a -> m a

  --| Multiplication of an N-by-N-element matrix and an N-element column vector
  vmul :: Num a => m a -> v a -> v a

但是,我不知道如何限制 type v。有可能做这样的事情吗?

请注意,我欢迎对声明多个相关类型的类型类的一般问题的回答,以及对为矩阵向量乘法声明类型类的具体问题的回答。在我的具体情况下,只有一小部分已知的 N 值(2、3 和 4),但我通常有兴趣了解在 Haskell 的类型系统中可以编码的内容。

编辑:我使用MultiParamTypeClassesFunctionalDependencies按照下面的 Gabriel Gonzalez 和 MFlamer 的建议实现了这一点。这就是我实现的相关部分最终的样子:

class MatrixVectorMultiplication m v | m -> v, v -> m where
  vmul :: Num a => m a -> v a -> v a

data Matrix3x3 a = ...
data Vector3 a = ...

instance MatrixVectorMultiplication Matrix3x3 Vector3 where
  vmul = ...

这是 的类型签名vmul,其本身和部分应用:

vmul :: (Num a, MatrixVectorMultiplication m v) => m a -> v a -> v a
(`vmul` v) :: Matrix3x3 Integer -> Vector3 Integer
(m `vmul`) :: Vector3 Integer -> Vector3 Integer

我觉得这一切都非常优雅。感谢您的回答!:)

4

4 回答 4

2

请注意,矩阵/向量维度可以使用类型级数字进行编码,这允许更通用的定义class MatrixNum (n :: Nat)而不是手动编码等Matrix3x3Matrix4x4同时还可以防止在编译类型乘法时再次出现不兼容的对象。在 GHC 7.4.* 中,它可以通过以下方式定义。

{-# LANGUAGE TypeFamilies, DataKinds, FlexibleInstances #-}

data Nat = Zero | Succ Nat

class MatrixNum (n :: Nat) where
    type Matrix n :: * -> *
    type Vector n :: * -> *
    mmul :: Num a => Matrix n a -> Matrix n a -> Matrix n a
    vmul :: Num a => Matrix n a -> Vector n a -> Vector n a

newtype ListMatrix (n :: Nat) a = ListMatrix [[a]] deriving Show
newtype ListVector (n :: Nat) a = ListVector [a] deriving Show

instance MatrixNum n where
    type Matrix n = ListMatrix n
    type Vector n = ListVector n
    mmul (ListMatrix xss) (ListMatrix yss) = ListMatrix $ error "Not implemented"
    vmul (ListMatrix xss) (ListVector ys) = ListVector $ error "Not implemented"

在 GHC 7.6.* 中甚至更好,它现在支持类型级提升文字,因此您可以删除上述Nat定义并在类型中使用NatGHC.TypeLits使用数字文字来指定对象的维度:

m1 :: ListMatrix 3 Int
m1 = ListMatrix [[1,2,3],[4,5,6],[7,8,9]]

v1 :: ListVector 3 Int
v1 = ListVector [1,2,3]

v2 = m1 `vmul` v1 -- has type ListVector 3 Int
于 2013-02-12T08:41:08.980 回答
1

这是对答案的一个非常小的变化MFlamer,这也使得m依赖于v

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class MatrixNxN m v | m -> v, v -> m where
    mmul :: Num a => m a -> m a -> m a
    vmul :: Num a => m a -> v a -> v a

这样,如果你这样做:

(`vmul` someVector)

...然后编译器可以仅根据someVector' 类型选择正确的实例。

类型族解决方案不会因为同样的原因而起作用,主要是因为如果你将类型构造函数声明为v类型构造函数的m类型函数,那么该类型函数不一定是一对一的,所以编译器不会是能够从m推断v。这就是为什么人们说函数依赖比类型族更强大的原因。

于 2013-02-12T01:17:22.450 回答
1

这就是我使用 MultiParamTypeClasses 完成此操作的方式,正如上面的回复中所建议的那样。您还需要使用 FunctionalDependencies 扩展,因为这两种类型都没有在每个类函数中使用。其他人可能会提供更完整的答案,但我最近一直在使用这种模式,所以我认为它可能会有所帮助。

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

module Test where

class MatrixNxN m v | m -> v where 
  mmul :: Num a => m a -> m a -> m a
  vmul :: Num a => m a -> v a -> v a
于 2013-02-11T21:56:46.390 回答
1

我想也可以使用类型类来实现它。为了避免循环函数依赖,我们声明向量和矩阵的类型取决于标量类型。虽然它需要newtype对标量以及它们对应的向量和矩阵都有一个,但它也有一些优点。特别是,我们不需要在andNum a的声明中保留约束。我们可以让实例实现对它们的标量值施加什么约束。mmulvmul

{-# LANGUAGE TypeFamilies #-}

class MatrixNxN a where
    data Matrix a :: *
    data Vector a :: *

    -- | Multiplication of two N-by-N-element matrices
    mmul :: Matrix a -> Matrix a -> Matrix a

    -- | Multiplication of an N-by-N-element matrix
    -- and an N-element column vector
    vmul :: Matrix a -> Vector a -> Vector a

-- List matrices on any kind of numbers:
newtype ListScalar a = ListScalar a
instance Num a => MatrixNxN (ListScalar a) where
    newtype Matrix (ListScalar a) = ListMatrix [[a]]
    newtype Vector (ListScalar a) = ListVector [a]

    vmul (ListMatrix mss)  (ListVector vs)   = ...
    mmul (ListMatrix m1ss) (ListMatrix m2ss) = ...


-- We can have matrices that have no `Num` instance for
-- their scalars, like Z2 implemented as `Bool`:
newtype ListBool = ListBool Bool
instance MatrixNxN ListBool where
    newtype Matrix ListBool = ListBoolMatrix [[Bool]]
    newtype Vector ListBool = ListBoolVector [Bool]

    vmul (ListBoolMatrix mss)  (ListBoolVector vs)   = ...
    mmul (ListBoolMatrix m1ss) (ListBoolMatrix m2ss) = ...
于 2013-02-12T08:07:24.927 回答