summer-0.3.2.0: src/Data/Prodder.hs
{-# LANGUAGE UndecidableSuperClasses #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE TypeFamilies #-}
{- |
Module: Data.Prodder
Copyright: (c) Samuel Schlesinger 2020
License: MIT
Maintainer: sgschlesinger@gmail.com
Stability: experimental
Portability: non-portable
Description: Extensible products
-}
module Data.Prodder
( -- * The extensible product type
Prod
-- * Construction and deconstruction
, extract
, index
, tailN
, initN
, dropFirst
, Consume(consume, extend1, cmap, produceB)
, produce
, empty
, FoldProd(foldProd)
, toList
, type ForAll
, atType
-- ** Efficient Iterative Construction
, buildProd
, ProdBuilder
, consB
, emptyB
, appendB
-- * Type families
, Index
, IndexIn
, HasIndexIn
, Consumer
, type (<>)
, Length
, Tail
, Init
, Replace
-- * Rearranging and removing elements
, Strengthen(strengthenP)
, strengthen
-- * Transforming extensible products
, remap
-- * Picking out individual components of a product
, Selection(select)
, type FieldsFromSelector
, type Selector
-- * Efficiently building 'Prod's
) where
import Data.ForAll (type ForAll)
import Control.Monad (unless)
import Control.Exception (catch, SomeException)
import GHC.Exts (Any, Constraint)
import Unsafe.Coerce (unsafeCoerce)
import Data.Functor.Identity (Identity (..))
import Data.Functor.Const (Const (..))
import Data.Vector (Vector)
import qualified Data.Vector as V
import qualified Data.Vector.Mutable as MV
import GHC.TypeLits (KnownNat, type (+), type (-), natVal, type (<=), Nat)
import Data.Proxy (Proxy(Proxy))
import Control.Monad.ST
import Data.STRef (newSTRef, STRef, writeSTRef, readSTRef)
-- | An extensible product type
newtype Prod (xs :: [*]) = UnsafeProd { unProd :: Vector Any }
-- | Lens to modify one element of a product.
atType :: forall a b xs f. (a `HasIndexIn` xs, Functor f) => (a -> f b) -> Prod xs -> f (Prod (Replace a b xs))
atType f (UnsafeProd v) = fmap (\b -> UnsafeProd $ v V.// [(fromIntegral i, unsafeCoerce b)]) (f (unsafeCoerce (v V.! fromIntegral i))) where
i = index @a @xs
-- | A type for constructing products with linear memory use.
newtype ProdBuilder (xs :: [*]) = UnsafeProdBuilder { unProdBuilder :: forall s. STRef s Int -> V.MVector s Any -> ST s () }
-- | Cons an element onto the head of a 'ProdBuilder'.
consB :: x -> ProdBuilder xs -> ProdBuilder (x ': xs)
consB x (UnsafeProdBuilder b) = UnsafeProdBuilder \ref v -> withIncrement ref \i -> do
MV.write v i (unsafeCoerce x)
-- | Empty 'ProdBuilder'.
emptyB :: ProdBuilder '[]
emptyB = UnsafeProdBuilder \_ _ -> pure ()
-- | Execute a 'ProdBuilder', pulling out a 'Prod'.
buildProd :: forall xs. (KnownNat (Length xs)) => ProdBuilder xs -> Prod xs
buildProd (UnsafeProdBuilder bs) = UnsafeProd $ V.create do
ref <- newSTRef 0
x <- MV.new (fromInteger $ natVal (Proxy @(Length xs)))
bs ref x
pure x
-- | Appends two 'ProdBuilder's.
appendB :: ProdBuilder xs -> ProdBuilder ys -> ProdBuilder (xs <> ys)
appendB (UnsafeProdBuilder b) (UnsafeProdBuilder b') = UnsafeProdBuilder \ref v -> do
b ref v
b' ref v
-- | Creates a 'ProdBuilder' with a single element.
singletonB :: x -> ProdBuilder '[x]
singletonB x = consB x emptyB
type role Prod representational
-- | A type family for computing the index of a type in a list of types.
type family IndexIn (x :: k) (xs :: [k]) where
IndexIn x (x ': xs) = 0
IndexIn x (y ': xs) = 1 + IndexIn x xs
-- | A type family for indexing into lists of types.
type family Index (n :: Nat) (xs :: [k]) where
Index 0 (x ': xs) = x
Index n (_ ': xs) = Index (n - 1) xs
-- | A type family for computing the length of a type level list
type family Length (xs :: [k]) where
Length '[] = 0
Length (x ': xs) = 1 + Length xs
-- | A type family for computing the tail of a type level list
type family Tail n xs where
Tail 0 xs = xs
Tail n (x ': xs) = Tail (n - 1) xs
-- | A type family for computing the initial segment of a type level list
type family Init n xs where
Init 0 xs = '[]
Init n (x ': xs) = x ': Init (n - 1) xs
-- | A class that is used for convenience in order to make certain type
-- signatures read more clearly.
class KnownNat (x `IndexIn` xs) => x `HasIndexIn` xs
instance KnownNat (x `IndexIn` xs) => x `HasIndexIn` xs
-- | Computes the index of the given type in the given type level list.
index :: forall x xs. x `HasIndexIn` xs => Word
index = fromInteger $ natVal (Proxy @(IndexIn x xs))
{-# INLINE CONLIKE index #-}
-- | Extract a value at a particular index
extract :: forall x xs. x `HasIndexIn` xs => Prod xs -> x
extract = getConst . atType @x @() @xs Const
{-# INLINE CONLIKE extract #-}
-- | Takes the tail of a product after the nth element.
tailN :: forall n xs. (KnownNat n, n <= Length xs) => Prod xs -> Prod (Tail n xs)
tailN (UnsafeProd v) = UnsafeProd $ V.slice n (V.length v - n) v
where
n = fromInteger $ natVal (Proxy @n)
{-# INLINE CONLIKE tailN #-}
-- | Takes the initial length n segment of a product.
initN :: forall n xs. (KnownNat n, n <= Length xs) => Prod xs -> Prod (Init n xs)
initN (UnsafeProd v) = UnsafeProd $ V.slice 0 n v
where
n = fromInteger $ natVal (Proxy @n)
{-# INLINE CONLIKE initN #-}
-- | Drop the first element of a product. Sometimes needed for better type
-- inference and less piping around of constraints.
dropFirst :: forall x xs. Prod (x ': xs) -> Prod xs
dropFirst (UnsafeProd v) = UnsafeProd $ V.slice 1 (V.length v - 1) v
type family (<>) (xs :: [k]) (ys :: [k]) :: [k] where
'[] <> ys = ys
(x ': xs) <> ys = x ': (xs <> ys)
combine :: forall xs ys. Prod xs -> Prod ys -> Prod (xs <> ys)
combine (UnsafeProd p) (UnsafeProd q) = UnsafeProd (p V.++ q)
{-# INLINE CONLIKE combine #-}
-- | Type family for replacing one type in a type level list with another
type family Replace x y xs where
Replace x y (x ': xs) = y ': xs
Replace x y (z ': xs) = z ': Replace x y xs
Replace x y '[] = '[]
-- | Replaces one type with another via a function
remap :: forall x y xs. x `HasIndexIn` xs => (x -> y) -> Prod xs -> Prod (Replace x y xs)
remap f = runIdentity . atType @x @y @xs (Identity . f)
{-# INLINE CONLIKE remap #-}
-- | This is a reified pattern match on an extensible product
type family Consumer xs r where
Consumer '[] r = r
Consumer (x ': xs) r = x -> Consumer xs r
-- | A typeclass that is useful to define the scott encoding/decoding for
-- extensible products.
class Consume xs where
consume :: forall r. Prod xs -> Consumer xs r -> r
produceB :: (forall r. Consumer xs r -> r) -> ProdBuilder xs
extend1 :: x -> Consumer xs (ProdBuilder (x ': xs))
cmap :: (r -> r') -> Consumer xs r -> Consumer xs r'
produce :: (KnownNat (Length xs), Consume xs) => (forall r. Consumer xs r -> r) -> Prod xs
produce f = buildProd $ produceB f
empty :: Prod '[]
empty = buildProd $ produceB id
instance Consume '[] where
consume = flip const
{-# INLINE CONLIKE consume #-}
produceB x = emptyB
{-# INLINE CONLIKE produceB #-}
extend1 x = consB x emptyB
{-# INLINE CONLIKE extend1 #-}
cmap f x = f x
{-# INLINE CONLIKE cmap #-}
withIncrement :: STRef s Int -> (Int -> ST s x) -> ST s x
withIncrement ref f = do
i <- readSTRef ref
x <- f i
writeSTRef ref (i + 1)
pure x
instance Consume xs => Consume (x ': xs) where
consume (UnsafeProd v) g = consume @xs (UnsafeProd (V.tail v)) $ g (unsafeCoerce $ v V.! 0)
{-# INLINE CONLIKE consume #-}
produceB g = g (extend1 @xs)
{-# INLINE CONLIKE produceB #-}
cmap f = fmap (cmap @xs f)
{-# INLINE CONLIKE cmap #-}
extend1 (x1 :: x1) x = cmap @xs @(ProdBuilder (x ': xs)) @(ProdBuilder (x1 ': x ': xs)) f (extend1 @xs x) where
f (UnsafeProdBuilder b) = UnsafeProdBuilder \ref v -> (withIncrement ref \i -> MV.write v i (unsafeCoerce x1)) >> b ref v
-- cmap @xs @(Prod (x ': xs)) @(Prod (x1 ': x ': xs)) (UnsafeProd . (V.singleton (unsafeCoerce x1) V.++) . unProd) (extend1 @xs x)
{-# INLINE CONLIKE extend1 #-}
instance Eq (Prod '[]) where
_ == _ = True
{-# INLINE CONLIKE (==) #-}
instance (Eq x, Eq (Prod xs)) => Eq (Prod (x ': xs)) where
px@(UnsafeProd x) == py@(UnsafeProd y) = unsafeCoerce @_ @x (x V.! 0) == unsafeCoerce @_ @x (y V.! 0) && dropFirst px == dropFirst py
{-# INLINE CONLIKE (==) #-}
-- | A typeclass to rearrange and possibly remove things from a product.
class Strengthen xs ys where
strengthenP :: Prod xs -> ProdBuilder ys
strengthen :: (KnownNat (Length ys), Strengthen xs ys) => Prod xs -> Prod ys
strengthen = buildProd . strengthenP
{-# INLINE CONLIKE strengthen #-}
instance (Strengthen xs ys, y `HasIndexIn` xs) => Strengthen xs (y ': ys) where
strengthenP p = consB (extract @y p) (strengthenP p)
{-# INLINE CONLIKE strengthenP #-}
instance Strengthen xs '[] where
strengthenP p = emptyB
{-# INLINE CONLIKE strengthenP #-}
-- | A typeclass for creating a selection function which is valid on the given definition.
type family Selector def fields a where
Selector def (field ': fields) a = Index field def -> Selector def fields a
Selector def '[] a = a
-- | Extracts the fields intended from the given selector type.
type family FieldsFromSelector def selector where
FieldsFromSelector def (a -> b) = IndexIn a def ': FieldsFromSelector def b
FieldsFromSelector def (Identity a) = '[]
-- | A class for constructing the select function inductively.
class Selection def selector a where
select :: Prod def -> selector -> a
instance Selection def a a where
select _prod s = s
{-# INLINE CONLIKE select #-}
instance (HasIndexIn x def, Selection def xs a) => Selection def (x -> xs) a where
select prod f = select @_ @_ @a prod (f (extract @x prod))
{-# INLINE CONLIKE select #-}
-- | A class for folding over a 'Prod' using a function which only requires that
-- every element of the product satisfy a certain constraint.
class ForAll c xs => FoldProd c xs where
foldProd :: Monoid m => (forall a. c a => a -> m) -> Prod xs -> m
instance FoldProd c '[] where
foldProd _f _p = mempty
instance (c x, FoldProd c xs) => FoldProd c (x ': xs) where
foldProd f p = f x <> foldProd @c f (dropFirst p) where
x = extract @x p
toList :: forall c xs a. FoldProd c xs => (forall x. c x => x -> a) -> Prod xs -> [a]
toList f = foldProd @c (pure . f)
class x `Contains` y where
eject :: x -> y