tmp-proc-0.5.0.0: src/System/TmpProc/TypeLevel.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_HADDOCK prune not-home #-}
{-|
Copyright : (c) 2020-2021 Tim Emiola
SPDX-License-Identifier: BSD3
Maintainer : Tim Emiola <adetokunbo@users.noreply.github.com>
Defines type-level data structures and combinators used by
"System.TmpProc.Docker" and "System.TmpProc.Warp".
'HList' implements a heterogenous list used to define types that represent
multiple concurrent @tmp procs@.
'KV' is intended for internal use within the @tmp-proc@ package. It allows
indexing and sorting of lists of tmp procs.
-}
module System.TmpProc.TypeLevel
( -- * Heterogenous List
HList(..)
, (&:)
, hHead
, hOf
, ReorderH(..)
-- * A type-level Key-Value
, KV(..)
, select
, selectMany
, LookupKV(..)
, MemberKV(..)
, ManyMemberKV(..)
-- * Other combinators
, IsAbsent
, IsInProof
-- * Re-exports
, module System.TmpProc.TypeLevel.Sort
)
where
import Data.Kind (Type)
import Data.Proxy (Proxy (..))
import GHC.Exts (Constraint)
import GHC.TypeLits (ErrorMessage (..), Symbol,
TypeError)
import qualified GHC.TypeLits as TL
import System.TmpProc.TypeLevel.Sort
-- $setup
-- >>> import Data.Proxy
-- >>> :set -XDataKinds
-- >>> :set -XTypeApplications
{-| Obtain the first element of a 'HList'. -}
hHead :: HList (a ': as) -> a
hHead (x `HCons` _) = x
{-| Get an item in an 'HList' given its type. -}
hOf :: forall y xs . IsInProof y xs => Proxy y -> HList xs -> y
hOf proxy = go proxy provedIsIn
where
go :: Proxy x -> IsIn x ys -> HList ys -> x
go _ IsHead (y `HCons` _) = y
go pxy (IsInTail cons) (_ `HCons` rest) = go pxy cons rest
{-| Defines a Heterogenous list. -}
data HList :: [*] -> * where
HNil :: HList '[]
HCons :: anyTy -> HList manyTys -> HList (anyTy ': manyTys)
infixr 5 `HCons`
infixr 5 &:
{-| An infix alias for 'HCons'. -}
(&:) :: x -> HList xs -> HList (x ': xs)
(&:) = HCons
instance Show (HList '[]) where
show HNil = "HNil"
instance (Show x, Show (HList xs)) => Show (HList (x ': xs)) where
show (HCons x xs) = show x ++ " &: " ++ show xs
instance Eq (HList '[]) where
HNil == HNil = True
instance (Eq x, Eq (HList xs)) => Eq (HList (x ': xs)) where
(HCons x xs) == (HCons y ys) = x == y && xs == ys
{-| Use a type-level symbol as /key/ type that indexes a /value/ type. -}
data KV :: Symbol -> * -> * where
V :: a -> KV s a
{-| A constraint that confirms that a type is not present in a type-level list. -}
type family IsAbsent e r :: Constraint where
IsAbsent e '[] = ()
IsAbsent e (e ': _) = TypeError (NotAbsentErr e)
IsAbsent e (e' ': tail) = IsAbsent e tail
type NotAbsentErr e =
('TL.Text " type " ':<>: 'TL.ShowType e) ':<>:
('TL.Text " is already in this type list, and is not allowed again")
{-| Proves a symbol and its type occur as entry in a list of @'KV'@ types. -}
data LookupKV (k :: Symbol) t (xs :: [*]) where
AtHead :: LookupKV k t (KV k t ': kvs)
OtherKeys :: LookupKV k t kvs -> LookupKV k t (KV ok ot ': kvs)
{-| Generate proof instances of 'LookupKV'. -}
class MemberKV (k :: Symbol) (t :: *) (xs :: [*]) where
lookupProof :: LookupKV k t xs
instance {-# Overlapping #-} MemberKV k t '[KV k t] where
lookupProof = AtHead @k @t @'[]
instance {-# Overlapping #-} MemberKV k t (KV k t ': kvs) where
lookupProof = AtHead @k @t @kvs
instance MemberKV k t kvs => MemberKV k t (KV ok ot ': kvs) where
lookupProof = OtherKeys lookupProof
{-| Select an item from an 'HList' of @'KV's@ by /key/.
/N.B/ Returns the first item. It assumes the keys in the KV HList are unique.
/TODO:/ enforce this rule using a constraint.
==== __Examples__
>>> select @"d" @Double @'[KV "b" Bool, KV "d" Double] (V True &: V (3.1 :: Double) &: HNil)
3.1
-}
select
:: forall k t xs . MemberKV k t xs
=> HList xs
-> t
select = go $ lookupProof @k @t @xs
where
go :: LookupKV k1 t1 xs1 -> HList xs1 -> t1
go AtHead (V x `HCons` _) = x
go (OtherKeys cons) (_ `HCons` y) = go cons y
{-| Proves that symbols with corresponding types occur as a 'KV' in a
list of 'KV' types
/Note/ - both the list symbols and @'KV'@ types need to be sorted, with @'KV'@
types sorted by key. /TODO:/ is there an easy way to incorporate this rule into
the proof ?
-}
data LookupMany (keys :: [Symbol]) (t :: [*]) (xs :: [*]) where
FirstOfMany :: LookupMany (k ': '[]) (t ': '[]) (KV k t ': kvs)
NextOfMany
:: LookupMany ks ts kvs
-> LookupMany (k ': ks) (t ': ts) (KV k t ': kvs)
ManyOthers :: LookupMany ks ts kvs -> LookupMany ks ts (KV ok ot ': kvs)
{-| Generate proof instances of 'LookupMany'. -}
class ManyMemberKV (ks :: [Symbol]) (ts :: [*]) (kvs :: [*]) where
manyProof :: LookupMany ks ts kvs
instance {-# Overlapping #-} ManyMemberKV '[k] '[t] (KV k t ': ks) where
manyProof = FirstOfMany @k @t @ks
instance {-# Overlapping #-} ManyMemberKV ks ts kvs => ManyMemberKV (k ': ks) (t ': ts) (KV k t ': kvs) where
manyProof = NextOfMany manyProof
instance ManyMemberKV ks ts kvs => ManyMemberKV ks ts (KV ok ot ': kvs) where
manyProof = ManyOthers manyProof
{-| Select items with specified keys from an @'HList'@ of @'KV's@ by /key/.
/N.B./ this this is an internal function.
The keys must be provided in the same order as they occur in the
HList, any other order will likely result in an compiler error.
==== __Examples__
>>> selectMany @'["b"] @'[Bool] @'[KV "b" Bool, KV "d" Double] (V True &: V (3.1 :: Double) &: HNil)
True &: HNil
-}
selectMany
:: forall ks ts xs . ManyMemberKV ks ts xs
=> HList xs
-> HList ts
selectMany = go $ manyProof @ks @ts @xs
where
go :: LookupMany ks1 ts1 xs1 -> HList xs1 -> HList ts1
go FirstOfMany (V x `HCons` _) = x `HCons` HNil
go (NextOfMany cons) (V x `HCons` y) = x `HCons` go cons y
go (ManyOthers cons) (_ `HCons` y) = go cons y
{-| Allows reordering of similar @'HList's@.
==== __Examples__
>>> hReorder @_ @'[Bool, Int] ('c' &: (3 :: Int) &: True &: (3.1 :: Double) &: HNil)
True &: 3 &: HNil
>>> hReorder @_ @'[Double, Bool, Int] ('c' &: (3 :: Int) &: True &: (3.1 :: Double) &: HNil)
3.1 &: True &: 3 &: HNil
-}
class ReorderH xs ys where
hReorder :: HList xs -> HList ys
instance ReorderH xs '[] where
hReorder _ = HNil
instance (IsInProof y xs, ReorderH xs ys) => ReorderH xs (y ': ys) where
hReorder xs = hOf @y Proxy xs `HCons` hReorder xs
{-| Proves a type is present in a list of other types. -}
data IsIn t (xs :: [Type]) where
IsHead :: IsIn t (t ': tys)
IsInTail :: IsIn t tys -> IsIn t (otherTy ': tys)
{-| Generate proof instances of 'IsIn'. -}
class IsInProof t (tys :: [Type]) where
provedIsIn :: IsIn t tys
instance {-# Overlapping #-} IsInProof t (t ': tys) where
provedIsIn = IsHead @t @tys
instance IsInProof t tys => IsInProof t (a : tys) where
provedIsIn = IsInTail provedIsIn