posable-1.0.0.0: src/Generics/POSable/Representation.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ExplicitNamespaces #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
-- | This module exports the `Product` and `Sum` type, and type- and valuelevel
-- functions on these types.
module Generics.POSable.Representation
( type (++)
, ProductType(..)
, concatPT
, Product(..)
, concatP
, SumType(..)
, Sum(..)
, Merge
, FoldMerge
, MapConcat
, Concat
, Ground(..)
, mergeT
, merge
, splitLeft
, splitRight
, unConcatP
, Undef(..)
) where
import Data.Kind
import Data.Typeable (Typeable)
import GHC.Generics as GHC
import Generics.SOP as SOP (All, All2, Generic)
-- | Concatenation of typelevel lists
type family (++) (xs :: [k]) (ys :: [k]) :: [k] where
'[] ++ ys = ys
(x ': xs) ++ ys = x ': xs ++ ys
infixr 5 ++
-- | The set of types that can exist in the sums.
-- This set can be extended by the user by providing an instance of Ground
-- for their types. The mkGround function gives a default value for the type.
-- Ground depends on Typeable, as this makes it possible for library users
-- to inspect the types of the contents of the sums.
class (Typeable a) => Ground a where
mkGround :: a
-----------------------------------------------------------------------
-- Heterogeneous lists with explicit types
-- | Type witness for `Product`
data ProductType :: [[Type]] -> Type where
PTNil :: ProductType '[]
PTCons :: SumType x -> ProductType xs -> ProductType (x ': xs)
instance (All2 Show (Map2TypeRep xs)) => Show (ProductType xs) where
show PTNil = "PTNil"
show (PTCons a as) = "PTCons" ++ show a ++ " (" ++ show as ++ ")"
-- | Concatenates `ProductType` values
concatPT :: ProductType x -> ProductType y -> ProductType (x ++ y)
concatPT PTNil ys = ys
concatPT (PTCons x xs) ys = PTCons x (concatPT xs ys)
-- | Typelevel product of `Sum`s with values
data Product :: [[Type]] -> Type where
Nil :: Product '[]
Cons :: Sum x -> Product xs -> Product (x ': xs)
deriving instance (All2 Eq xs) => Eq (Product xs)
instance (All2 Show xs) => Show (Product xs) where
show Nil = "Nil"
show (Cons a as) = "Cons " ++ show a ++ " (" ++ show as ++ ")"
-- | Concatenates `Product` values
concatP :: Product x -> Product y -> Product (x ++ y)
concatP Nil ys = ys
concatP (Cons x xs) ys = Cons x (concatP xs ys)
-- | Type witness for `Sum`
data SumType :: [Type] -> Type where
STSucc :: (Ground x) => x -> SumType xs -> SumType (x ': xs)
STZero :: SumType '[]
-- | Typelevel sum, contains one value from the typelevel list of types, or
-- undefined.
data Sum :: [Type] -> Type where
Pick :: (Ground x) => x -> Sum (x ': xs)
Skip :: (Ground x) => Sum xs -> Sum (x ': xs)
data Undef = Undef
deriving (Eq, Show, GHC.Generic, SOP.Generic)
-- Undef is the only default Ground, because we need to mark when no value
-- is when 2 non-equal-lenght types are zipped
instance Ground Undef where
mkGround = Undef
deriving instance (All Eq xs) => Eq (Sum xs)
type family MapTypeRep (xs :: [Type]) :: [Type] where
MapTypeRep '[] = '[]
MapTypeRep (x ': xs) = x ': MapTypeRep xs
type family Map2TypeRep (xss :: [[Type]]) :: [[Type]] where
Map2TypeRep '[] = '[]
Map2TypeRep (x ': xs) = MapTypeRep x ': Map2TypeRep xs
instance (All Show (MapTypeRep xs)) => Show (SumType xs) where
show (STSucc x xs) = "STSucc" ++ show x ++ "(" ++ show xs ++ ")"
show STZero = "STZero"
instance (All Show x) => Show (Sum x) where
show (Pick x) = "Pick " ++ show x
show (Skip x) = "Skip " ++ show x
-- only used in examples
data A
data B
data C
data D
data E
data F
data G
data H
----------------------------------------
-- Type functions on lists
-- | Concatenate a list of lists, typelevel equivalent of
--
-- > concat :: [[a]] -> [a]`
--
-- Example:
--
-- >>> :kind! Concat '[ '[A, B], '[C, D]]
-- Concat '[ '[A, B], '[C, D]] :: [Type]
-- = '[A, B, C, D]
--
type family Concat (xss :: [[x]]) :: [x] where
Concat '[] = '[]
Concat (xs ': xss) = xs ++ Concat xss
-- | Map `Concat` over a list (of lists, of lists), typelevel equivalent of
--
-- > map . concat :: [[[a]]] -> [[a]]
--
-- Example:
--
-- >>> :kind! MapConcat '[ '[ '[A, B], '[C, D]], '[[E, F], '[G, H]]]
-- MapConcat '[ '[ '[A, B], '[C, D]], '[[E, F], '[G, H]]] :: [[Type]]
-- = '[ '[A, B, C, D], '[E, F, G, H]]
--
type family MapConcat (xsss :: [[[x]]]) :: [[x]] where
MapConcat '[] = '[]
MapConcat (xss ': xsss) = Concat xss ': MapConcat xsss
-- | Zip two lists of lists with ++` as operator, while keeping the length of
-- the longest outer list
--
-- Example:
--
-- >>> :kind! Merge '[ '[A, B, C], '[D, E]] '[ '[F, G]]
-- Merge '[ '[A, B, C], '[D, E]] '[ '[F, G]] :: [[Type]]
-- = '[ '[A, B, C, F, G], '[D, E]]
--
type family Merge (xs :: [[Type]]) (ys :: [[Type]]) :: [[Type]] where
Merge '[] '[] = '[]
Merge '[] (b ': bs) = (Undef ': b) ': Merge '[] bs
Merge (a ': as) '[] = (a ++ '[Undef]) ': Merge as '[]
Merge (a ': as) (b ': bs) = (a ++ b) ': Merge as bs
-- | Fold `Merge` over a list (of lists, of lists)
--
-- Example:
--
-- >>> :kind! FoldMerge '[ '[ '[A, B, C], '[D, E]], '[ '[F, G]], '[ '[H]]]
-- FoldMerge '[ '[ '[A, B, C], '[D, E]], '[ '[F, G]], '[ '[H]]] :: [[Type]]
-- = '[ '[A, B, C, F, G, H], '[D, E]]
--
type family FoldMerge (xss :: [[[Type]]]) :: [[Type]] where
FoldMerge '[a] = a
FoldMerge (a ': as) = Merge a (FoldMerge as)
FoldMerge '[] = '[]
----------------------------------------
-- Functions on Products and Sums
-- | Merge a `ProductType` and a `Product`, putting the values of the `Product` in
-- the right argument of `Merge`
zipSumRight :: ProductType l -> Product r -> Product (Merge l r)
zipSumRight PTNil Nil = Nil
zipSumRight PTNil (Cons y ys) = Cons (takeRightUndef y) (zipSumRight PTNil ys)
zipSumRight (PTCons x xs) Nil = Cons (makeUndefRight x) (zipSumRight xs Nil)
zipSumRight (PTCons x xs) (Cons y ys) = Cons (takeRight x y) (zipSumRight xs ys)
makeUndefRight :: SumType x -> Sum (x ++ '[Undef])
makeUndefRight (STSucc _ xs) = Skip (makeUndefRight xs)
makeUndefRight STZero = Pick Undef
makeUndefLeft :: SumType x -> Sum (Undef ': x)
makeUndefLeft _ = Pick Undef
takeRightUndef :: Sum r -> Sum (Undef ': r)
takeRightUndef = Skip
takeLeftUndef :: Sum x -> Sum (x ++ '[Undef])
takeLeftUndef (Pick x) = Pick x
takeLeftUndef (Skip xs) = Skip (takeLeftUndef xs)
-- | Merge a `ProductType` and a `Product`
merge :: Either (Product l, ProductType r) (ProductType l, Product r) -> Product (Merge l r)
merge (Left (l, r)) = zipSumLeft l r
merge (Right (l, r)) = zipSumRight l r
-- | Merge a `ProductType` and a `Product`, putting the values of the `Product`
-- in the left argument of `Merge`
zipSumLeft :: Product l -> ProductType r -> Product (Merge l r)
zipSumLeft Nil PTNil = Nil
zipSumLeft Nil (PTCons y ys) = Cons (makeUndefLeft y) (zipSumLeft Nil ys)
zipSumLeft (Cons x xs) PTNil = Cons (takeLeftUndef x) (zipSumLeft xs PTNil)
zipSumLeft (Cons x xs) (PTCons y ys) = Cons (takeLeft x y) (zipSumLeft xs ys)
-- | Merge two `ProductType`s
mergeT :: ProductType l -> ProductType r -> ProductType (Merge l r)
mergeT PTNil PTNil = PTNil
mergeT PTNil (PTCons y ys) = PTCons (makeUndefLeftT y) (mergeT PTNil ys)
mergeT (PTCons x xs) PTNil = PTCons (makeUndefRightT x) (mergeT xs PTNil)
mergeT (PTCons x xs) (PTCons y ys) = PTCons (takeST x y) (mergeT xs ys)
makeUndefRightT :: SumType x -> SumType (x ++ '[Undef])
makeUndefRightT (STSucc x xs) = STSucc x (makeUndefRightT xs)
makeUndefRightT STZero = STSucc Undef STZero
makeUndefLeftT :: SumType x -> SumType (Undef ': x)
makeUndefLeftT = STSucc Undef
takeST :: SumType l -> SumType r -> SumType (l ++ r)
takeST (STSucc l ls) rs = STSucc l (takeST ls rs)
takeST STZero rs = rs
takeLeft :: Sum l -> SumType r -> Sum (l ++ r)
takeLeft (Pick l) _ = Pick l
takeLeft (Skip ls) rs = Skip (takeLeft ls rs)
takeRight :: SumType l -> Sum r -> Sum (l ++ r)
takeRight (STSucc _ ls) rs = Skip (takeRight ls rs)
takeRight STZero rs = rs
-- | UnMerge a `Product`, using two `ProductType`s as witnesses for the left and
-- right argument of `Merge`. Produces a value of type Product right
splitRight :: Product (Merge l r) -> ProductType l -> ProductType r -> Product r
splitRight (Cons x xs) PTNil (PTCons _ rs) = Cons (removeUndefLeft x) (splitRight xs PTNil rs)
splitRight _ _ PTNil = Nil
splitRight (Cons x xs) (PTCons l ls) (PTCons r rs) = Cons (splitSumRight x l r) (splitRight xs ls rs)
removeUndefLeft :: Sum (Undef ': x) -> Sum x
removeUndefLeft (Pick Undef) = error "Undefined value where I expected an actual value"
removeUndefLeft (Skip xs) = xs
removeUndefRight :: SumType x -> Sum (x ++ '[Undef]) -> Sum x
removeUndefRight STZero _ = error "Undefined value where I expected an actual value"
removeUndefRight (STSucc _ _) (Pick y) = Pick y
removeUndefRight (STSucc _ xs) (Skip ys) = Skip (removeUndefRight xs ys)
-- | UnMerge a `Product`, using two `ProductType`s as witnesses for the left and
-- right argument of `Merge`. Produces a value of type Product left
splitLeft :: Product (Merge l r) -> ProductType l -> ProductType r -> Product l
splitLeft _ PTNil _ = Nil
splitLeft (Cons x xs) (PTCons l ls) PTNil = Cons (removeUndefRight l x) (splitLeft xs ls PTNil)
splitLeft (Cons x xs) (PTCons l ls) (PTCons r rs) = Cons (splitSumLeft x l r) (splitLeft xs ls rs)
splitSumRight :: Sum (l ++ r) -> SumType l -> SumType r -> Sum r
splitSumRight xs STZero _ = xs
splitSumRight (Pick _) (STSucc _ _) _ = error "No value found in right side of Sum"
splitSumRight (Skip xs) (STSucc _ ls) rs = splitSumRight xs ls rs
splitSumLeft :: Sum (l ++ r) -> SumType l -> SumType r -> Sum l
splitSumLeft (Pick x) (STSucc _ _) _ = Pick x
splitSumLeft _ STZero _ = error "No value value found in left side of Sum"
splitSumLeft (Skip xs) (STSucc _ ls) rs = Skip $ splitSumLeft xs ls rs
-- | UnConcat a `Product`, using a `ProductType` as the witness for the first
-- argument of `++`. Produces a tuple with the first and second argument of `++`
unConcatP :: Product (x ++ y) -> ProductType x -> (Product x, Product y)
unConcatP xs PTNil = (Nil, xs)
unConcatP (Cons x xs) (PTCons _ ts) = (Cons x xs', ys')
where
(xs', ys') = unConcatP xs ts