Если вы проверите тип ваших add
и sub
, вы увидите проблему.
ghci> :t add
add :: Num a => [[a]] -> [[a]] -> [[a]]
ghci> :t sub
sub :: Num a => [[a]] -> [[a]] -> [[a]]
Михаилу было предложено по существу развернуть 2D-список и перенести его в методы экземпляра Num.,Еще один способ сделать это - изменить ваши методы add
и sub
для работы с матрицами.Здесь я использую подход «подъема», когда я пишу комбинаторы, чтобы «поднять» функцию из одного типа в другой.
-- unwraps the 2d list from a matrix
unMatrix :: Matrix a -> [[a]]
unMatrix (Matrix m) = m
-- lifts a 2d list operation to be a Matrix operation
liftMatrixOp :: ([[a]] -> [[a]] -> [[a]]) -> Matrix a -> Matrix a -> Matrix a
liftMatrixOp f x y = Matrix $ f (unMatrix x) (unMatrix y)
-- lifts a regular operation to be a 2d list operation
lift2dOp :: (a -> a -> a) -> [[a]] -> [[a]] -> [[a]]
lift2dOp f = zipWith (zipWith f)
С этими комбинаторами определение add
и sub
просто вопросподъема соответственно.
add, sub :: Num a => Matrix a -> Matrix a -> Matrix a
add = liftMatrixOp add2D
sub = liftMatrixOp sub2D
add2D, sub2D :: Num a => [[a]] -> [[a]] -> [[a]]
add2D = lift2dOp (+)
sub2D = lift2dOp (-)
Теперь, когда у нас есть функции, которые работают с матрицами, экземпляр Num прост:
instance (Num a) => Num (Matrix a) where
(+) = add
(-) = sub
..etc..
Конечно, мы могли бы объединить lift2dOp
и liftMatrixOp
в одну удобную функцию:
-- lifts a regular operation to be a Matrix operation
liftMatrixOp' :: (a -> a -> a) -> Matrix a -> Matrix a -> Matrix a
liftMatrixOp' = liftMatrixOp . lift2dOp
instance (Num a) => Num (Matrix a) where
(+) = liftMatrixOp' (+)
(-) = liftMatrixOp' (-)
(*) = liftMatrixOp' (*)
..etc..
Теперь вы попробуйте: определить liftMatrix :: (a -> a) -> Matrix a -> Matrix a
, функцию подъема для унарных функций.Теперь используйте это для определения negate
, abs
и signum
.Документы предполагают, что abs x * signum x
всегда должно быть эквивалентно x
.Посмотрите, верно ли это для нашей реализации.
ghci> quickCheck (\xs -> let m = Matrix xs in abs m * signum m == m)
+++ OK, passed 100 tests.
На самом деле, если вы напишите liftMatrix
с более мягкой подписью типа, это можно использовать для определения экземпляра Functor
для Matrices.
liftMatrix :: (a -> b) -> Matrix a -> Matrix b
instance Functor (Matrix a) where
fmap = liftMatrix
Теперь подумайте, как вы могли бы реализовать fromInteger
.Реализация этого позволяет вам делать такие вещи в ghci:
ghci> Matrix [[1,2],[3,4]] + 1
Matrix [[2,3],[4,5]]
Вот так все работает так, как я это реализовал, в любом случае.Помните, что любой числовой литерал n
в коде на Haskell фактически преобразуется в fromInteger n
, поэтому это работает.
Я думаю, на данный момент это достаточно весело, но если вам нужно больше упражнений, попробуйте освоитьсяэто Arbitrary
экземпляр Matrices:
instance Arbitrary a => Arbitrary (Matrix a) where
arbitrary = liftM Matrix arbitrary