Сегодня в Великобритании выходной, поэтому у меня было время закончить ответ о матрицах статического размера. Я бы не рекомендовал делать это в продакшене — даже если не принимать во внимание глупость кода, это ужасное представление матриц, если вы хотите выполнять эффективную линейную алгебру на реальном оборудовании — но это довольно весело. возиться с.
Из Хазохизм:
-- Natural numbers and their singletons in explicit and implicit varieties
data Nat = Z | S Nat -- page 2 of the paper
intToNat :: Int -> Maybe Nat -- paraphrased from page 10
intToNat n
| n < 0 = Nothing
| n == 0 = Just Z
| otherwise = S <$> intToNat (n-1)
data Natty n where -- page 2
Zy :: Natty Z
Sy :: Natty n -> Natty (S n)
-- page 3
class NATTY n where
natty :: Natty n
instance NATTY Z where
natty = Zy
instance NATTY n => NATTY (S n) where
natty = Sy natty
-- turn an explicit Natty into an implicit one
natter :: Natty n -> (NATTY n => r) -> r -- page 4
natter Zy r = r
natter (Sy n) r = natter n r
-- vectors, matrices in row-major order
data Vec n a where -- page 2
V0 :: Vec Z a
(:>) :: a -> Vec n a -> Vec (S n) a
newtype Mat w h a = Mat { unMat :: Vec h (Vec w a) } -- page 4
-- vector addition, in the form of an Applicative instance
vcopies :: Natty n -> a -> Vec n a -- page 4
vcopies Zy x = V0
vcopies (Sy n) x = :> vcopies n x
vapp :: Vec n (a -> b) -> Vec n a -> Vec n b -- page 4
vapp V0 V0 = V0
vapp (f :> fs) (x :> xs) = f x :> vapp fs xs
instance NATTY n => Applicative (Vec n) where -- page 4
pure = vcopies natty
(<*>) = vapp
-- iterating vectors
instance Traversable (Vec n) where -- page 4
traverse f V0 = pure V0
traverse f (x :> xs) = liftA2 (:>) (f x) (traverse f xs)
instance Foldable (Vec n) where -- page 4
foldMap = foldMapDefault
instance Functor (Vec n) where -- page 4
fmap = fmapDefault
transpose :: NATTY w => Mat w h a -> Mat h w a -- page 4
transpose = Mat . sequenceA . unMat
Я позволил себе переименовать авторский тип Matrix
в Mat
, переставить его аргументы типа и изменить его с GADT на новый тип. Простите меня за то, что я пропускаю объяснение вышеизложенного - статья работает лучше, чем я мог бы, и я хочу перейти к той части, где я отвечаю на ваш вопрос.
Mat w h
является h
-вектором из w
-векторов. Это композиция на уровне типов. двух функторов Vec
. Его экземпляр Applicative
, который реализует сложение матриц, отражает эту структуру,
instance (NATTY w, NATTY h) => Applicative (Mat w h) where
pure = Mat . pure . pure
Mat fss <*> Mat xss = Mat $ liftA2 (<*>) fss xss
как и его экземпляр Traversable
.
instance Traversable (Mat w h) where
traverse f = fmap Mat . traverse (traverse f) . unMat
instance Foldable (Mat w h) where
foldMap = foldMapDefault
instance Functor (Mat w h) where
fmap = fmapDefault
Также нам понадобится немного оборудования для работы с индексами векторов. Чтобы идентифицировать конкретный элемент в n
-векторе, вы должны указать число меньше n
.
data Fin n where
FZ :: Fin (S n)
FS :: Fin n -> Fin (S n)
Тип Fin n
содержит ровно n
элементов, поэтому Fin
— это семейство конечных множеств. Значение типа Fin n
структурно является натуральным числом, меньшим n
(сравните FS FZ
с S Z
), поэтому FS FZ :: Fin (S (S Z))
или FS FZ :: Fin (S (S (S Z)))
, но FS FZ :: Fin (S Z)
не пройдет проверку типа.
Вот функция более высокого порядка, которая строит вектор, содержащий все возможные результаты своего аргумента.
tabulate :: Natty n -> (Fin n -> a) -> Vec n a
tabulate Zy f = V0
tabulate (Sy n) f = f FZ :> tabulate n (f . FS)
Теперь можно приступить к работе с полукольцами. Скалярное произведение двух векторов включает в себя умножение их элементов и последующее суммирование результата.
dot :: Semiring a => Vec n a -> Vec n a -> a
dot xs ys = foldr (<+>) zero $ vapp (fmap (<.>) xs) ys
Вот вектор, который равен zero
везде, кроме указанного индекса.
oneAt :: Semiring a => Natty n -> Fin n -> Vec n a
oneAt (Sy n) FZ = one :> vcopies n zero
oneAt (Sy n) (FS f) = zero :> oneAt n f
Мы будем использовать oneAt
и tabulate
для создания матрицы идентичности.
type Square n = Mat n n
identity :: Semiring a => Natty n -> Square n a
identity n = Mat $ tabulate n (oneAt n)
ghci> identity (Sy (Sy Zy)) :: Square (S (S Z)) Int
Mat {unMat = (1 :> (0 :> V0)) :> ((0 :> (1 :> V0)) :> V0)}
-- ┌ ┐
-- │ 1, 0 │
-- │ 0, 1 │
-- └ ┘
И transpose
пригодится для умножения матриц.
mul :: (NATTY w, Semiring a) => Mat r h a -> Mat w r a -> Mat w h a
mul m n =
let mRows = unMat m
nCols = unMat $ transpose n
in Mat $ fmap (\r -> dot r <$> nCols) mRows
ghci> let m = Mat $ (1 :> 2 :> V0) :> (3 :> 4 :> V0) :> V0 :: Square (S (S Z)) Int in mul m m
Mat {unMat = (7 :> (10 :> V0)) :> ((15 :> (22 :> V0)) :> V0)}
-- ┌ ┐2 ┌ ┐
-- │ 1, 2 │ = │ 7, 10 │
-- │ 3, 4 │ │ 15, 22 │
-- └ ┘ └ ┘
Итак, экземпляр Semiring
для квадратных матриц отсортирован. Фу!
instance (NATTY n, Semiring a) => Semiring (Square n a) where
zero = pure zero
(<+>) = liftA2 (<+>)
one = identity natty
(<.>) = mul
Что следует отметить в этой реализации, так это то, что zero
и one
динамически создают матрицы статически известного размера, обычно на основе контекстной информации о типе в месте вызова. Они получают представление этого размера во время выполнения (Natty
) из словаря NATTY
, который разработчик строит на основе предполагаемого типа матрицы.
Это совершенно другой подход, чем в библиотеке reflection
(о которой я рассказал в другом моем ответе). reflection
— это вставка явных значений времени выполнения в неявные словари экземпляров, тогда как этот стиль берет информацию, которая в противном случае была бы известна только во время выполнения — размер матрицы — и делает ее статической, используя одиночные элементы, чтобы сделать информацию о типе доступной в мире значений. . Конечно, настоящий язык с зависимой типизацией обошелся бы с Natty
церемонией: n
было бы обычным старым значением, и мы могли бы использовать его напрямую, вместо того, чтобы проходить через синглетон, скрытый в словаре экземпляров.
Я оставлю вам алгебру Клини, потому что я ленив и хочу перейти к вопросу синтеза информации о типах на основе ввода во время выполнения.
Как мы можем использовать эти матрицы со статическими размерами, если мы не знаем размер статически? Вы упомянули, что ваша программа спрашивает пользователя, насколько велик его граф (и, следовательно, насколько велика матрица смежности, используемая для представления графа). Итак, пользователь вводит число (Nat
-значение, а не Nat
-тип), и мы каким-то образом должны статически знать, что пользователь собирался ввести?
Хитрость заключается в написании блоков кода, которые не зависят от значения размера матрицы. Затем, независимо от входных данных, если это было натуральное число, мы знаем, что этот блок кода будет работать. Мы можем заставить функцию быть полиморфной, используя типы более высокого ранга.
withNatty :: Nat -> (forall n. Natty n -> r) -> r
withNatty Z r = r Zy
withNatty (S n) r = withNatty n (r . Sy)
withNatty n r
применяет функцию r
к одноэлементному представлению натурального числа n
. r
имеет Natty n
, доступный во время выполнения, поэтому он может восстановить статические знания n
по шаблону, соответствующему Natty
, но n
не может просочиться за пределы блока. (Вы также можете использовать экзистенциальную количественную оценку, которая кратко описана в Хазохизме, чтобы обернуть Natty
и передавать ее повсюду. Это то же самое.)
Так, например, предположим, что мы хотим напечатать единичную матрицу динамически определяемого размера:
main = do
Just size <- fmap intToNat readLn
withNatty size (print . mkIdentity)
where mkIdentity :: Natty n -> Square n Int
mkIdentity n = natter n one
ghci> main
4
Mat {unMat = (1 :> (0 :> (0 :> (0 :> V0)))) :> ((0 :> (1 :> (0 :> (0 :> V0)))) :> ((0 :> (0 :> (1 :> (0 :> V0)))) :> ((0 :> (0 :> (0 :> (1 :> V0)))) :> V0)))}
Тот же метод применяется, если вы хотите, скажем, построить матрицу из списка списков. На этот раз это немного сложнее, потому что вам нужно доказать GHC, что все списки имеют одинаковую длину, путем их измерения.
withVec :: [a] -> (forall n. NATTY n => Vec n a -> r) -> r
withVec [] r = r V0
withVec (x:xs) r = withVec xs (r . (x :>))
-- this operation can fail because the input lists may not all be the same length
withMat :: [[a]] -> (forall w h. (NATTY w, NATTY h) => Mat w h a -> r) -> Maybe r
withMat xss r = assertEqualLengths xss (\vs -> withVec vs (r . Mat))
where assertEqualLengths :: [[a]] -> (forall n. NATTY n => [Vec n a] -> r) -> Maybe r
assertEqualLengths [] r = Just (r noVecs)
assertEqualLengths xss@(xs:_) r = withLen xs (\n -> natter n $ r <$> traverse (assertLength n) xss)
noVecs :: [Vec Z a]
noVecs = []
assertLength :: Natty n -> [a] -> Maybe (Vec n a)
assertLength Zy [] = Just V0
assertLength (Sy n) (x:xs) = fmap (x :>) (assertLength n xs)
assertLength _ _ = Nothing
withLen :: [a] -> (forall n. Natty n -> r) -> r
withLen [] r = r Zy
withLen (x:xs) r = withLen xs (r . Sy)
ghci> withMat [[1,2], [3,4]] show
Just "Mat {unMat = (1 :> (2 :> V0)) :> ((3 :> (4 :> V0)) :> V0)}"
ghci> withMat [[1,2], [3]] show -- a ragged input list
Nothing
И если вы хотите работать с квадратными матрицами, вы должны доказать GHC, что высота матрицы равна ее ширине.
withEqual :: Natty n -> Natty m -> (n ~ m => r) -> Maybe r
withEqual Zy Zy r = Just r
withEqual (Sy n) (Sy m) r = withEqual n m r
withEqual _ _ _ = Nothing
square :: Natty w -> Natty h -> Mat w h a -> Maybe (Square w a)
square = withEqual
person
Benjamin Hodgson♦
schedule
01.05.2017
Applicative
; придумайте новые имена функций, которые принимают ваши дополнительные параметры размера. (Помещаете ли вы эти функции в класс или нет, зависит от ваших потребностей.) Но в зависимости от того, какие библиотечные операции уже доступны для вещей, поддерживающихApplicative
, это может быть болезненно... вы будете знать больше, чем мы, о том, является ли это не заводится или нет. Что вы думаете? - person Daniel Wagner   schedule 27.04.2017Applicative
,Semiring
иStarSemiring
, безусловно, является вариантом, хотя я считаю его умеренно болезненным и неэлегантным. Я сохраню это как резервный вариант в зависимости от других ответов. - person Pete   schedule 27.04.2017reflection
.) Это очень разные требования! - person Benjamin Hodgson♦   schedule 27.04.2017data Matrix :: (Nat,Nat) -> * -> * where Mat :: {unMat :: Vec h (Vec w a)} -> Matrix '(w,h) a
@pigworker: как, например, выглядит экземплярApplicative
? Если я хочу определить синоним типа, такой какtype AdjMat = Matrix Int (Tropical Double)
от O'Connor, могу ли я? Что эквивалентноtoMatrix :: NATTY n => [[a]] -> Matrix (Pair n n) a
? Спасибо! - person Pete   schedule 28.04.2017data Matrix i e = Pure e | Matrix (Array (i,i) e)
? - person Daniel Wagner   schedule 28.04.2017one :: Matrix i e
Semiring
, который в версииBounded
реализованone = buildMatrix (\(i,j) -> if i == j then one else zero)
. (Если бы я использовал вашу идею выше,buildMatrix
был бы параметризован по размеру, поэтому вместо этогоoneM :: (A.Ix i, Enum i, Num i, Semiring e) => i -> Matrix i e
.) - person Pete   schedule 28.04.2017