【问题标题】:Declaring a type class for multiplication of an N-by-N-element matrix and an N-element column vector为 N 乘 N 元素矩阵和 N 元素列向量的乘法声明类型类
【发布时间】:2013-01-26 12:05:46
【问题描述】:

在 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

但是,我不知道如何约束 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

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

【问题讨论】:

  • 有趣的是,您会使用“family”这个词,因为“type family”扩展正是您正在寻找的。另请参阅“关联类型”。
  • 您还需要说明您是否希望mv 唯一确定。
  • @luqui 好吧,那怎么样。我会调查的。谢谢!
  • @GabrielGonzalez 是的,我的意图是,对于每种类型的 N 元素向量,将只有一种类型的 N×N 元素矩阵。
  • 另一种可能性是使用 hmatrix 库中 Mul 类型类的样式中的 MultiParamTypeClassesFunctionalDependencies

标签: haskell typeclass functional-dependencies


【解决方案1】:

请注意,矩阵/向量维度可以使用类型级数字进行编码,这允许更通用的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.* 中甚至更好,它现在支持 type-level promoted literals,因此您可以删除上面的 Nat 定义并使用 GHC.TypeLits 中的 Nat 并在类型中使用数字文字来指定对象的尺寸:

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

【讨论】:

    【解决方案2】:

    这是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 类型构造函数的类型函数,则该类型函数不一定是一对一的。 1,因此编译器将无法从v 推断出m。这就是为什么人们说函数依赖比类型族更强大的原因。

    【讨论】:

    • 虽然类型族不可能,但数据族确实会创建一对一的映射。不过,与他们合作有点痛苦。
    【解决方案3】:

    这就是我使用 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
    

    【讨论】:

      【解决方案4】:

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

      {-# 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) = ...
      

      【讨论】:

        猜你喜欢
        • 2021-12-31
        • 1970-01-01
        • 1970-01-01
        • 2011-12-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-09
        • 1970-01-01
        相关资源
        最近更新 更多