在Write this Scala Matrix multiplication in Haskell进行冗长的讨论之后,我想知道......类型安全矩阵乘法会是什么样子?所以这是你的挑战:要么链接到Haskell实现,要么自己实现,如下:
data Matrix ... = ...
matrixMult :: Matrix ... -> Matrix ... -> Matrix ...
matrixMult ... = ...
如果您尝试将两个具有不兼容维度的基质相乘,matrixMult
在编译时会产生类型错误。布朗尼指出,如果你链接到讨论这个精确主题的论文或书籍,和/或讨论自己这个功能有用/无用。
答案 0 :(得分:11)
有很多软件包可以实现这个目标:
特别是维修论文对设计空间和选择进行了非常好的讨论:http://repa.ouroborus.net/
历史感兴趣的是McBride的"Faking It",它描述了强类型向量。他采用的技术与上述包装中使用的技术非常相似。他们显然是在做出依赖类型编程的圈子里知道的,但我的印象是“Faking It”论文是在Haskell中使用它们的早期实例之一。 Oleg的2005 Monad Reader article on number-parameterized类型也对这些技术的历史进行了一些很好的讨论。
答案 1 :(得分:11)
您可以使用类型级自然数来对维度进行编码。您的矩阵类型变为
-- x, y: Dimensions
data Matrix x y n = ...
你必须定义两个额外的ADT
和一个类TLN
(Type Level Naturals):
data Zero
data Succ a
class TLN a where fromTLN :: a -> Int
instance TLN Zero where fromTLN = const Zero
instance TLN a => TLN (Succ a) where fromTLN = 1 + fromTLN (undefined :: a)
你的功能类型非常简单:
matrixMult :: (TLN x, TLN y, TLN t, Num a) =>
Matrix x t a -> Matrix t y a -> Matrix x y a
您可以通过生成undefined
相应类型以及ScopedTypeVariables
扩展名来提取数组维度。
此代码完全未经测试,GHC可能会编译。这只是一个关于如何做到的草图。
答案 2 :(得分:10)
抱歉,无法抗拒粘贴我很久以前掀起的东西。这是在类型族之前,所以我使用fundeps进行算术运算。我确认这仍适用于GHC 7。
{-# LANGUAGE EmptyDataDecls,
ScopedTypeVariables,
MultiParamTypeClasses,
FunctionalDependencies,
FlexibleContexts,
FlexibleInstances,
UndecidableInstances #-}
import System.IO
-- Peano type numerals
data Z
data S a
type One = S Z
type Two = S One
type Three = S Two
class Nat a
instance Nat Z
instance Nat a => Nat (S a)
class Positive a
instance Nat a => Positive (S a)
class Pred a b | a -> b
instance Pred (S a) a
-- Vector type
newtype Vector n k = Vector {unVector :: [k]}
deriving (Read, Show, Eq)
empty :: Vector Z k
empty = Vector []
vtail :: Pred s' s => Vector s' k -> Vector s k
vtail (Vector (a:as)) = Vector as
vhead :: Positive s => Vector s k -> k
vhead (Vector (a:as)) = a
liftV :: (a->b) -> Vector s a -> Vector s b
liftV f = Vector . map f . unVector
type Matrix m n k = Vector m (Vector n k)
infixr 6 |>
(|>) :: k -> Vector s k -> Vector (S s) k
k |> v = Vector . (k:) . unVector $ v
-- Arithmetic
instance (Num k) => Num (Vector n k) where
(+) (Vector v) (Vector u) = Vector $ zipWith (+) v u
(*) (Vector v) (Vector u) = Vector $ zipWith (*) v u
abs = liftV abs
signum = liftV signum
dot :: Num k => Vector n k -> Vector n k -> k
dot u v = sum . unVector $ v*u
class Transpose n m where
transpose :: Matrix n m k -> Matrix m n k
instance (Transpose m a, Nat a, Nat m) => Transpose m (S a) where
transpose v = liftV vhead v |>
transpose (liftV vtail v)
instance Transpose m Z where
transpose v = empty
multiply :: (Nat n, Nat m, Nat n', Num k, Transpose m n) =>
Matrix m n k -> Matrix n' m k -> Matrix n n' k
multiply a (Vector bs) = Vector [Vector [a `dot` b | a <- as] | b <- bs]
where (Vector as) = transpose a
printMatrix :: Show k => Matrix m n k -> IO ()
printMatrix = mapM_ (putStrLn) . map (show.unVector) . unVector
-- Examples
m :: Matrix Three Three Integer
m = (1 |> 2 |> 3 |> empty)
|> (2 |> 3 |> 4 |> empty)
|> (3 |> 4 |> 5 |> empty) |> empty
n :: Matrix Three Two Integer
n = (1 |> 0 |> empty)
|> (0 |> 1 |> empty)
|> (1 |> 1 |> empty) |> empty
o = multiply n m
p = multiply n (transpose n)
答案 3 :(得分:0)
更多的是Haskell-idiomatic,实际上并不是说 matrices ,其维度只是一个数字,几乎没有告诉你关于映射的结构 /它的空格之间的地图。相反,矩阵乘法最好被视为 Vect k 类别中的类别 - 合成操作。矢量空间自然由Haskell类型表示; vector-space
library已经有很长时间了。
作为线性函数的组合,维度检查是对Haskell函数组合所做的类型检查的必然结果。不仅如此,你还可以区分不同的空间尽管具有相同的维度可能是不兼容的 - 例如,矩阵本身形成一个向量空间(张量空间),但是3×的空间3个矩阵与9个元素向量的空间并不完全兼容。在Matlab和其他“数组语言”中,处理线性映射空间上的线性映射需要在不同等级的张量之间进行容易出错的重塑;当然,我们不希望在Haskell中使用它!
有一个问题:要有效地实现这些功能,你不能只在任何空间之间拥有函数,但需要一种仍然像矩阵一样的底层表示。这仅适用于所有允许的空间实际上是向量空间的情况,因此您不能使用the standard Category
class,因为这需要任何两种类型之间的id
。相反,你需要一个实际上限于向量空间的类别类。但现在在现代的Haskell中表达并不是很难。
走过这条路线的两个图书馆是: