Обычный способ — обернуть не очень типизированное представление в более типизированное, например:
data Nat = Z | S Nat
newtype Vec (n :: Nat) a = Vec [a]
newtype Sized m (ns :: [Nat]) a = Sized { getArray :: Array (Vec m Int) a }
Здесь ns
— это продвигаемый (см. Продвижение Haskell) фантом (см. Мотивация фантомных типов?) значение — список размеров измерений, а m
— это длина этого списка (расширенного и фантомного). Таким образом, любой массив под оболочкой Sized
считается многомерной матрицей, размеры которой представляют ns
. Экземпляр Monoid
выглядит следующим образом:
instance (SingI m, SingI ns, Monoid a) => Monoid (Sized m ns a) where
mempty = listSized $ repeat mempty
Sized as `mappend` Sized bs = listSized $ zipWith mappend (elems as) (elems bs)
Этот SingI
материал взят из библиотеки singletons. Синглтоны позволяют поднимать значения на уровень типов, поэтому мы можем как бы эмулировать зависимые типы, а SingI
позволяет возвращать поднятые значения обратно на уровень значений с помощью функции fromSing
. listSized
по сути является listArray
, но для массивов со статически известными размерами и, следовательно, требует, чтобы все эти SingI
находились в области видимости. Вот его определение:
toInt :: Nat -> Int
toInt = go 0 where
go !a Z = a
go a (S n) = go (1 + a) n
vecBounds :: forall m (ns :: [Nat]). (SingI m) => Sing ns -> (Vec m Int, Vec m Int)
vecBounds singNs = (Vec $ replicate m 0, Vec ns') where
m = toInt $ fromSing (sing :: Sing m)
ns' = map (pred . toInt) $ fromSing singNs
listSized :: forall m (ns :: [Nat]) a. (SingI m, SingI ns) => [a] -> Sized m ns a
listSized = Sized . listArray (vecBounds (sing :: Sing ns))
vecBounds
вычисляет границы для заданного расширенного списка размеров измерений. Он возвращает кортеж, первый компонент которого является самым нижним индексом, который всегда имеет форму [0,0..0]
(нулей столько, сколько размеров, т.е. m
). Второй компонент является наибольшим индексом, поэтому, если вы, например. есть список размеров таких измерений, как [2, 1, 3]
(представленный как [S (S Z), S Z, S (S (S Z))]
), тогда максимальный индекс равен [1, 0, 2]
.
Остается только предоставить экземпляр Ix
для Vec n a
, который является прямым обобщением экземпляры продукта:
instance Ix a => Ix (Vec n a) where
range (Vec ns, Vec ms) = map Vec . sequence $ zipWith (curry range) ns ms
index (Vec ns, Vec ms) (Vec ps) = foldr (\(i, r) a -> i + r * a) 0 $
zipWith3 (\n m p -> (index (n, m) p, rangeSize (n, m))) ns ms ps
inRange (Vec ns, Vec ms) (Vec ps) = and $ zipWith3 (curry inRange) ns ms ps
И мы можем написать несколько тестов:
type M = S (S (S Z))
type Ns = [S (S Z), S Z, S (S (S Z))]
arr1 :: Sized M Ns (Sum Int)
arr1 = listSized $ map Sum [5,3,6,7,1,4]
arr2 :: Sized M Ns (Sum Int)
arr2 = listSized $ map Sum [8,2,9,7,3,6]
main = mapM_ (print . getArray) $ [arr1, arr2, arr1 `mappend` arr2 `mappend` mempty]
Это печатает
array (Vec [0,0,0],Vec [1,0,2]) [(Vec [0,0,0],Sum {getSum = 5}),(Vec [0,0,1],Sum {getSum = 6}),(Vec [0,0,2],Sum {getSum = 1}),(Vec [1,0,0],Sum {getSum = 3}),(Vec [1,0,1],Sum {getSum = 7}),(Vec [1,0,2],Sum {getSum = 4})]
array (Vec [0,0,0],Vec [1,0,2]) [(Vec [0,0,0],Sum {getSum = 8}),(Vec [0,0,1],Sum {getSum = 9}),(Vec [0,0,2],Sum {getSum = 3}),(Vec [1,0,0],Sum {getSum = 2}),(Vec [1,0,1],Sum {getSum = 7}),(Vec [1,0,2],Sum {getSum = 6})]
array (Vec [0,0,0],Vec [1,0,2]) [(Vec [0,0,0],Sum {getSum = 13}),(Vec [0,0,1],Sum {getSum = 15}),(Vec [0,0,2],Sum {getSum = 4}),(Vec [1,0,0],Sum {getSum = 5}),(Vec [1,0,1],Sum {getSum = 14}),(Vec [1,0,2],Sum {getSum = 10})]
т.е. элементы суммировались поточечно по мере необходимости. И если вы случайно попытаетесь суммировать массивы с разными размерностями, вы получите ошибку типа:
type Ns = [S (S Z), S Z, S (S (S Z))]
type Ns' = [S (S (S Z)), S Z, S (S Z)]
arr1 :: Sized M Ns (Sum Int)
arr1 = listSized $ map Sum [5,3,6,7,1,4]
arr2 :: Sized M Ns' (Sum Int)
arr2 = listSized $ map Sum [8,2,9,7,3,6]
main = print . getArray $ arr1 `mappend` arr2
-- Couldn't match type 'S 'Z with 'Z …
-- Expected type: Sized M Ns (Sum Int)
-- Actual type: Sized M Ns' (Sum Int)
-- In the second argument of `mappend', namely `arr2'
-- In the first argument of `mappend', namely `arr1 `mappend` arr2'
Полный код.
person
user3237465
schedule
10.10.2016