Car*_*man 6 haskell matrix typeclass
我用C型和Lisp型语言编程了几十年,而Haskell用了几年.现在为了进一步理解类型和Haskell的其他漂亮的高级功能,例如并行性,我一直在尝试创建一个带有矩阵语义的新数据类型,现在可以在库的Array类型之上进行背负.
我正在使用最新的Haskell Platform 2013.2.0.0和附带的ghc.
newtype Matrix a = Matrix (Array (Int,Int) a)
Run Code Online (Sandbox Code Playgroud)
(我知道数据将起作用而不是newtype,但在这种情况下,你会得到相同的语义,而使用newtype则会获得更好的性能).
例如,创建单位矩阵的一些简单函数可以正常工作:
unityMatrix:: (Num a) => Int -> Matrix a
unityMatrix d = Matrix $ let b = ((0,0),(d-1,d-1)) in array b
[((i,j),if i==j then 1 else 0)|(i,j)<-range b]
Run Code Online (Sandbox Code Playgroud)
创建基本的show函数也是如此:
instance Show a => Show (Matrix a) where
show (Matrix x) =
let
b = bounds x
rb = ((fst.fst) b, (fst.snd) b)
cb = ((snd.fst) b, (snd.snd) b)
in
intercalate "\n" [unwords ([show (x!(r,c))|c<-range cb])|r<-range rb]
Run Code Online (Sandbox Code Playgroud)
然后,为了使常规算术运算符起作用,我添加:
instance Num a => Num (Matrix a) where
fromInteger x =
let
b = ((0,0),(0,0))
in Matrix $ array b
[((i,j),if i == j then (fromInteger x) else 0)|(i,j)<-range b]
(Matrix x) + (Matrix y) =
let
b = bounds x
in
if b /= bounds y then error "Unmatched matrix addition" else Matrix $ array b
[(ij,x!ij + y!ij)|ij<-range b]
signum (Matrix x) =
let
b = bounds x
in Matrix $ array b
[(ij,signum (x!ij))|ij<-range b]
abs (Matrix x) =
let
b = bounds x
in Matrix $ array b
[(ij,abs (x!ij))|ij<-range b]
(Matrix x) - (Matrix y) =
let
b = bounds x
in
if b /= bounds y then error "Unmatched matrix subtraction" else Matrix $ array b
[(ij,x!ij - y!ij)|ij<-range b]
(Matrix x) * (Matrix y) =
let
b = (((fst.fst.bounds) x, (fst.snd.bounds) x),((snd.fst.bounds) y, (snd.snd.bounds) y))
kb = ((snd.fst.bounds) x, (snd.snd.bounds) x)
in
if kb /= ((fst.fst.bounds) y, (fst.snd.bounds) y) then error "Unmatched matrix multiplication" else Matrix $ array b
[((i,j),sum [(x!(i,k)) * (y!(k,j))|k<-range kb])|(i,j)<-range b]
Run Code Online (Sandbox Code Playgroud)
(如果这里的缩进被搞砸了,我很抱歉 - 实际代码正确缩进并编译.)
到目前为止,这么好,虽然必须定义一个在矩阵语义中没有任何意义的fromInteger函数有点烦人,但创建一个带有该值的1x1矩阵与其他任何东西一样合理.
我的问题是试图获得标量(即Num类型类型)与矩阵相乘的正确语义.通过数学约定,这意味着只是与标量的元素乘法.
但是,无论我尝试什么语法,我都没有得到正确的结果.默认情况下,此实现仅使用fromInteger将(例如)Int提升为矩阵(除非Matrix已经是1x1),否则会导致"不匹配的矩阵乘法"错误.我已经尝试了所有可以考虑的替代语法来为这种类型的乘法定义替代代码而没有成功,例如:
(Matrix x) * (y::Int) =
Run Code Online (Sandbox Code Playgroud)
要么
(Matrix x) * (Num y) =
Run Code Online (Sandbox Code Playgroud)
要么
(*):: (Num a) => Matrix -> a -> Matrix
Run Code Online (Sandbox Code Playgroud)
但所有这些都给了我各种语法错误.
我应该如何通过矩阵乘法来定义标量,以便它能够完成你期望它做的事情?我觉得启用非标准模式防护功能可能有所帮助,但我不太确定如何在这种情况下正确使用它.
我意识到我可能只是特殊情况下的矩阵乘法,以允许任何矩阵乘以1x1矩阵,我猜这将有效.但那将是(a)不优雅,(b)un-Haskell-y,(c)可能是低效的,因为它需要在乘法之前将每个标量包装到矩阵中,并且(d)允许某些代码(例如任意矩阵与任何1x1矩阵的乘法运算,当它应该导致错误时运行.
我也明白,可能有出色的Matrix实现,它们以某种方式回避了这个问题.但是使用它们会破坏我学习的目的.
“您正在寻找的语义”与 HaskellNum类的定义方式不一致。Haskell 中不存在数据提升之类的东西(原因非常充分,很难解释,但随着您使用 Haskell 获得的经验越多,很容易发现)。
我不建议定义一个新的*,也不建议您自己定义具有签名的操作员名称a -> Matrix a -> Matrix a,这会令人困惑。相反,您应该查看已定义此操作的位置:正如 user5402 所定义的那样,这是一个标量乘法。显然,这不仅对矩阵/线性运算符有意义,而且对向量也有意义。看哪,这是向量空间的类!
但就像 Ganesh 和 Odomontois 所建议的那样Num,您还需要扩展数据类型才能正确使用它。基本问题是矩阵乘法实际上仅定义用于匹配协变逆变维度,但您的方法无法确保这一点。理想情况下,类型检查器应该推断出正确的维度,但您可以有一种特殊情况,不仅适用于恒等矩阵,还适用于一般对角矩阵。
data LinOp a = Diagonal a\n | GMatrix (Matrix a)\n\ninstance Functor LinOp where\n fmap f (Diagonal a) = Diagonal (f a)\n fmap f (GMatrix m) = GMatrix $ fmap f m\n\ninstance (Num a) => AdditiveGroup (Matrix a) where\n zeroV = Diagonal 0\n negateV = fmap negate\n (Diagonal x) ^+^ (Diagonal y) = Diagonal $ x+y\n ...\n\ninstance (Num a) => VectorSpace (Matrix a) where\n type Scalar (Matrix a) = a\n \xce\xbc *^ m = fmap (\xce\xbc*) m\n\ninstance (Num a) => Num (Matrix a) where\n fromInteger = Diagonal . fromInteger\n (+) = (^+^)\n ...\nRun Code Online (Sandbox Code Playgroud)\n