posable-1.0.0.0: src/Generics/POSable/POSable.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE ExplicitNamespaces #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NoStarIsType #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
-- | Exports the `POSable` class, which has a generic implementation `GPOSable`.
-- Also re-exports Generic.SOP, which is needed to derive POSable.
module Generics.POSable.POSable (POSable(..), Generic, Finite) where
import Data.Finite (combineProduct, combineSum,
separateProduct, separateSum)
import Generics.POSable.Representation
import Generics.SOP hiding (Nil, shift)
import Generics.SOP.NP hiding (Nil)
import Data.Kind (Type)
import qualified Generics.SOP as SOP
import GHC.Base (Nat)
import GHC.TypeLits (KnownNat, natVal, type (*),
type (+))
import Data.Finite.Internal
-- | POSable, the base of this library. Provide a compact memory representation
-- for a type and a function to get back to the original type.
-- This memory representation consist of `choices`, that represent all
-- constructor choices in the type in a single Finite integer, and `fields`
-- which represents all values in the type as a Product of Sums, which can
-- be mapped to a struct-of-arrays representation for use in array-based
-- languages like Accelerate.
class (KnownNat (Choices x)) => POSable x where
type Choices x :: Nat
type Choices x = GChoices (SOP I (Code x))
choices :: x -> Finite (Choices x)
default choices ::
( Generic x
, GPOSable (SOP I (Code x))
, GChoices (SOP I (Code x)) ~ Choices x
) => x -> Finite (Choices x)
choices x = gchoices $ from x
-- | The `tags` function returns the range of each constructor.
-- A few examples:
-- >>> tags @Bool
-- [1,1]
-- >>> tags @(Either Float Float)
-- [1,1]
-- >>> tags @(Bool, Bool)
-- [4]
-- >>> tags @(Either Bool Bool)
-- [2,2]
tags :: [Integer]
default tags ::
( GPOSable (SOP I (Code x))
) => [Integer]
tags = gtags @(SOP I (Code x))
fromPOSable :: Finite (Choices x) -> Product (Fields x) -> x
default fromPOSable ::
( Generic x
, GPOSable (SOP I (Code x))
, Fields x ~ GFields (SOP I (Code x))
, Choices x ~ GChoices (SOP I (Code x))
) => Finite (Choices x) -> Product (Fields x) -> x
fromPOSable cs fs = to $ gfromPOSable cs fs
type Fields x :: [[Type]]
type Fields x = GFields (SOP I (Code x))
fields :: x -> Product (Fields x)
default fields ::
( Generic x
, Fields x ~ GFields (SOP I (Code x))
, GPOSable (SOP I (Code x))
) => x -> Product (Fields x)
fields x = gfields $ from x
emptyFields :: ProductType (Fields x)
default emptyFields ::
( GPOSable (SOP I (Code x))
, Fields x ~ GFields (SOP I (Code x))
) => ProductType (Fields x)
emptyFields = gemptyFields @(SOP I (Code x))
-----------------------------------------------------------------------
-- | Generic implementation of POSable,
class (KnownNat (GChoices x)) => GPOSable x where
type GChoices x :: Nat
gchoices :: x -> Finite (GChoices x)
gtags :: [Integer]
type GFields x :: [[Type]]
gfields :: x -> Product (GFields x)
gfromPOSable :: Finite (GChoices x) -> Product (GFields x) -> x
gemptyFields :: ProductType (GFields x)
-----------------------------------------------------------------------
-- Generic instance for POSable
instance
( All2 POSable xss
, KnownNat (Sums (MapProducts (Map2Choices xss)))
, All2 KnownNat (Map2Choices xss)
, All KnownNat (MapProducts (Map2Choices xss))
, KnownNat (Length xss)
) => GPOSable (SOP I xss) where
type GChoices (SOP I xss) = Sums (MapProducts (Map2Choices xss))
gchoices x = sums $ mapProducts $ map2choices $ unSOP x
gtags = getTags (pureChoices2 @xss)
type GFields (SOP I xss) = FoldMerge (MapConcat (Map2Fields xss))
gfields (SOP x) = foldMerge
(mapConcatT (pureMap2Fields @xss))
(mapConcat (map2Fields x))
gemptyFields = foldMergeT $ mapConcatT (pureMap2Fields @xss)
gfromPOSable cs fs = SOP (mapFromPOSable cs' fs (pureConcatFields @xss))
where
cs' = unSums cs (pureChoices2 @xss)
getTags :: All KnownNat (MapProducts (Map2Choices xss)) => NP ProductsMapChoices xss -> [Integer]
getTags SOP.Nil = []
getTags (ProductsMapChoices x :* xs) = natVal x : getTags xs
--------------------------------------------------------------------------------
-- Supporting types and classes
--------------------------------------------------------------------------------
type family Length (xs :: [x]) :: Nat where
Length '[] = 0
Length (x ': xs) = Length xs + 1
type family MapLength (xss :: [[x]]) :: [Nat] where
MapLength '[] = '[]
MapLength (x ': xs) = Length x ': MapLength xs
type family MapChoices (xs :: [Type]) :: [Nat] where
MapChoices '[] = '[]
MapChoices (x ': xs) = Choices x ': MapChoices xs
type family Map2Choices (xss :: [[Type]]) :: [[Nat]] where
Map2Choices '[] = '[]
Map2Choices (xs ': xss) = MapChoices xs ': Map2Choices xss
type family MapFields (xs :: [Type]) :: [[[Type]]] where
MapFields '[] = '[]
MapFields (x ': xs) = Fields x ': MapFields xs
type family Map2Fields (xss :: [[Type]]) :: [[[[Type]]]] where
Map2Fields '[] = '[]
Map2Fields (xs ': xss) = MapFields xs ': Map2Fields xss
type family Products (xs :: [Nat]) :: Nat where
Products '[] = 1
Products (x ': xs) = x * Products xs
type family MapProducts (xss :: [[Nat]]) :: [Nat] where
MapProducts '[] = '[]
MapProducts (xs ': xss) = Products xs ': MapProducts xss
type family Sums (xs :: [Nat]) :: Nat where
Sums '[] = 0
Sums (x ': xs) = x + Sums xs
--------------------------------------------------------------------------------
-- Functions that deal with Choices
--------------------------------------------------------------------------------
mapChoices :: forall xs . (All POSable xs) => NP I xs -> NP Finite (MapChoices xs)
mapChoices SOP.Nil = SOP.Nil
mapChoices (x :* xs) = choices (unI x) :* mapChoices xs
map2choices :: (All2 POSable xss) => NS (NP I) xss -> NS (NP Finite) (Map2Choices xss)
map2choices (Z x) = Z (mapChoices x)
map2choices (S xs) = S (map2choices xs)
sums :: All KnownNat xs => NS Finite xs -> Finite (Sums xs)
sums (Z y) = combineSum (Left y)
sums (S ys) = combineSum (Right (sums ys))
mapProducts :: (All2 KnownNat xss) => NS (NP Finite) xss -> NS Finite (MapProducts xss)
mapProducts (Z x) = Z (combineProducts x)
mapProducts (S xs) = S (mapProducts xs)
combineProducts :: (All KnownNat xs) => NP Finite xs -> Finite (Products xs)
combineProducts SOP.Nil = 0
combineProducts (y :* ys) = combineProduct (y, combineProducts ys)
--------------------------------------------------------------------------------
-- Functions that deal with Fields
--------------------------------------------------------------------------------
mapFields :: forall xs . (All POSable xs) => NP I xs -> NP Product (MapFields xs)
mapFields SOP.Nil = SOP.Nil
mapFields (x :* xs) = fields (unI x) :* mapFields xs
map2Fields :: (All2 POSable xss) => NS (NP I) xss -> NS (NP Product) (Map2Fields xss)
map2Fields (Z x) = Z (mapFields x)
map2Fields (S xs) = S (map2Fields xs)
mapConcat :: NS (NP Product) xss -> NS Product (MapConcat xss)
mapConcat (Z x) = Z (appends x)
mapConcat (S xs) = S (mapConcat xs)
appends :: NP Product xs -> Product (Concat xs)
appends SOP.Nil = Nil
appends (x :* xs) = concatP x (appends xs)
foldMerge :: NP ProductType xss -> NS Product xss -> Product (FoldMerge xss)
foldMerge (_ :* SOP.Nil) (Z y) = y
foldMerge (_ :* SOP.Nil) (S _) = error "Reached an inaccessible pattern"
foldMerge SOP.Nil _ = Nil
foldMerge (_ :* x' :* xs) (Z y) = merge $ Left (y, foldMergeT (x' :* xs))
foldMerge (x :* x' :* xs) (S ys) = merge $ Right (x, foldMerge (x' :* xs) ys)
appendsT :: NP ProductType xs -> ProductType (Concat xs)
appendsT SOP.Nil = PTNil
appendsT (x :* xs) = concatPT x (appendsT xs)
mapConcatT :: NP (NP ProductType) xss -> NP ProductType (MapConcat xss)
mapConcatT SOP.Nil = SOP.Nil
mapConcatT (x :* xs) = appendsT x :* mapConcatT xs
foldMergeT :: NP ProductType xss -> ProductType (FoldMerge xss)
foldMergeT (x :* SOP.Nil) = x
foldMergeT SOP.Nil = PTNil
foldMergeT (x :* x' :* xs) = mergeT x (foldMergeT (x' :* xs))
--------------------------------------------------------------------------------
-- Functions that deal with creating Products from types
newtype ProductFields a = ProductFields (Product (Fields a))
newtype ProductFieldsT a = ProductFieldsT (ProductType (Fields a))
newtype ProductConcatFieldsT a = ProductConcatFieldsT (ProductType (Concat (MapFields a)))
newtype ProductMapFieldsT a = ProductMapFieldsT (NP ProductType (MapFields a))
pureMapFields :: forall xs . (All POSable xs) => NP ProductType (MapFields xs)
pureMapFields = convert $ pureFields @xs
where
convert :: NP ProductFieldsT ys -> NP ProductType (MapFields ys)
convert SOP.Nil = SOP.Nil
convert (ProductFieldsT x :* xs) = x :* convert xs
pureFields :: (All POSable xs) => NP ProductFieldsT xs
pureFields = cpure_NP (Proxy :: Proxy POSable) pureProductFields
where
pureProductFields :: forall x . (POSable x) => ProductFieldsT x
pureProductFields = ProductFieldsT $ emptyFields @x
pureMap2Fields :: forall xss . (All2 POSable xss) => NP (NP ProductType) (Map2Fields xss)
pureMap2Fields = convert $ pure2Fields @xss
where
convert :: NP ProductMapFieldsT yss -> NP (NP ProductType) (Map2Fields yss)
convert SOP.Nil = SOP.Nil
convert (ProductMapFieldsT x :* xs) = x :* convert xs
pure2Fields :: (All2 POSable zss) => NP ProductMapFieldsT zss
pure2Fields = cpure_NP (Proxy :: Proxy (All POSable)) pureProductMapFieldsT
where
pureProductMapFieldsT :: forall xs . (All POSable xs) => ProductMapFieldsT xs
pureProductMapFieldsT = ProductMapFieldsT $ pureMapFields @xs
pureConcatFields :: forall xss . (All2 POSable xss) => NP ProductConcatFieldsT xss
pureConcatFields = convert $ pure2Fields @xss
where
convert :: NP ProductMapFieldsT yss -> NP ProductConcatFieldsT yss
convert SOP.Nil = SOP.Nil
convert (ProductMapFieldsT x :* xs) = ProductConcatFieldsT (appendsT x) :* convert xs
--------------------------------------------------------------------------------
-- Functions that deal with creating Choices from types
newtype FChoices a = FChoices (Finite (Choices a))
newtype ProductsMapChoices a = ProductsMapChoices (Finite (Products (MapChoices a)))
newtype FMapChoices a = FMapChoices (NP Finite (MapChoices a))
pureChoices2 :: forall xss . (All2 KnownNat (Map2Choices xss)) => (All2 POSable xss) => NP ProductsMapChoices xss
pureChoices2 = convert $ pure2Choices @xss
where
convert :: (All2 KnownNat (Map2Choices yss)) => NP FMapChoices yss -> NP ProductsMapChoices yss
convert SOP.Nil = SOP.Nil
convert (FMapChoices x :* xs) = ProductsMapChoices (combineProducts x) :* convert xs
pureMapChoices :: forall xs . (All POSable xs) => NP Finite (MapChoices xs)
pureMapChoices = convert $ pureChoices @xs
where
convert :: NP FChoices ys -> NP Finite (MapChoices ys)
convert SOP.Nil = SOP.Nil
convert (FChoices x :* xs) = x :* convert xs
pureChoices :: (All POSable xs) => NP FChoices xs
pureChoices = cpure_NP (Proxy :: Proxy POSable) (FChoices 0)
pure2Choices :: (All2 POSable xss) => NP FMapChoices xss
pure2Choices = cpure_NP (Proxy :: Proxy (All POSable)) pureFMapChoices
where
pureFMapChoices :: forall xs . (All POSable xs) => FMapChoices xs
pureFMapChoices = FMapChoices $ pureMapChoices @xs
-------------------------------------------------------
-- Functions to get back to the SOP representation
separateProducts :: (All KnownNat (MapChoices xs)) => ProductsMapChoices xs -> NP FChoices xs -> NP FChoices xs
separateProducts _ SOP.Nil = SOP.Nil
separateProducts (ProductsMapChoices x) (_ :* ys) = FChoices x' :* separateProducts (ProductsMapChoices xs) ys
where
(x', xs) = separateProduct x
zipFromPOSable :: All POSable xs => NP FChoices xs -> NP ProductFields xs -> NP I xs
zipFromPOSable SOP.Nil SOP.Nil = SOP.Nil
zipFromPOSable (FChoices c :* cs) (ProductFields f :* fs) = I (fromPOSable c f) :* zipFromPOSable cs fs
foldMergeT2 :: NP ProductConcatFieldsT xss -> ProductType (FoldMerge (MapConcat (Map2Fields xss)))
foldMergeT2 (ProductConcatFieldsT x :* SOP.Nil) = x
foldMergeT2 SOP.Nil = PTNil
foldMergeT2 (ProductConcatFieldsT x :* x' :* xs) = mergeT x (foldMergeT2 (x' :* xs))
unSums :: (All KnownNat (MapProducts (Map2Choices xs))) => Finite (Sums (MapProducts (Map2Choices xs))) -> NP ProductsMapChoices xs -> NS ProductsMapChoices xs
unSums _ SOP.Nil = error "Cannot construct empty sum"
unSums x (_ :* ys) = case separateSum x of
Left x' -> Z (ProductsMapChoices x')
Right x' -> S (unSums x' ys)
mapFromPOSable
:: forall xss .
( All2 KnownNat (Map2Choices xss)
, All2 POSable xss
) => NS ProductsMapChoices xss
-> Product (FoldMerge (MapConcat (Map2Fields xss)))
-> NP ProductConcatFieldsT xss
-> NS (NP I) xss
mapFromPOSable (Z cs) fs fts@(_ :* _ :* _) = Z (zipFromPOSable cs' ( unConcat (unMergeLeft fs fts) pureFields))
where
cs' = separateProducts cs pureChoices
mapFromPOSable (S cs) fs fts@(_ :* _ :* _) = S (mapFromPOSable cs (unMergeRight fs fts) (tl fts))
mapFromPOSable (Z cs) fs (_ :* SOP.Nil) = Z (zipFromPOSable cs' (unConcat fs pureFields))
where
cs' = separateProducts cs pureChoices
mapFromPOSable (S cs) _ fts@(_ :* SOP.Nil) = S (mapFromPOSable cs Nil (tl fts))
unConcat :: Product (Concat (MapFields xs)) -> NP ProductFieldsT xs -> NP ProductFields xs
unConcat Nil SOP.Nil = SOP.Nil
unConcat xs (ProductFieldsT ys :* yss) = ProductFields x' :* unConcat xs' yss
where
(x', xs') = unConcatP xs ys
unMergeLeft :: forall xs xss . Product (Merge (Concat (MapFields xs)) (FoldMerge (MapConcat (Map2Fields xss)))) -> NP ProductConcatFieldsT (xs ': xss) -> Product (Concat (MapFields xs))
unMergeLeft xs (ProductConcatFieldsT y :* ys) = splitLeft xs y (foldMergeT2 @xss ys)
unMergeRight :: forall xs xss . Product (Merge (Concat (MapFields xs)) (FoldMerge (MapConcat (Map2Fields xss)))) -> NP ProductConcatFieldsT (xs ': xss) -> Product (FoldMerge (MapConcat (Map2Fields xss)))
unMergeRight xs (ProductConcatFieldsT y :* ys) = splitRight xs y (foldMergeT2 @xss ys)