recover-rtti-0.1.0.0: src/Debug/RecoverRTTI/Util/TypeLevel.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Debug.RecoverRTTI.Util.TypeLevel (
-- * Singletons
Sing(..)
, SingI(..)
, DecidableEquality(..)
-- ** Natural numbers
, Nat(..)
, knownNat
, Length
-- * General purpose type level functions
, Or
, Equal
, Elem
, Assert
-- * Type-level membership check
, IsElem(..)
, checkIsElem
-- * Phantom type parameters
, Phantom(..)
, Poly(..)
, maybePoly
) where
import Data.Kind
import Data.Proxy
import Data.Type.Equality
import GHC.TypeLits (ErrorMessage, Symbol, KnownSymbol, TypeError, sameSymbol)
{-------------------------------------------------------------------------------
Singletons
-------------------------------------------------------------------------------}
data family Sing :: k -> Type
class SingI (a :: k) where
sing :: Sing a
class DecidableEquality k where
decideEquality :: Sing (a :: k) -> Sing (b :: k) -> Maybe (a :~: b)
{-------------------------------------------------------------------------------
For kind 'Type', Sing is just a proxy
-------------------------------------------------------------------------------}
data instance Sing (a :: Type) where
SProxy :: Sing (a :: Type)
instance SingI (a :: Type) where
sing = SProxy
{-------------------------------------------------------------------------------
Natural numbers
Unlike @ghc@'s, these are inductively defined.
-------------------------------------------------------------------------------}
data Nat = Z | S Nat
data instance Sing (n :: Nat) where
SZ :: Sing 'Z
SS :: Sing n -> Sing ('S n)
instance SingI 'Z where sing = SZ
instance SingI n => SingI ('S n) where sing = SS sing
knownNat :: Sing (n :: Nat) -> Int
knownNat SZ = 0
knownNat (SS n) = knownNat n + 1
type family Length (xs :: [k]) :: Nat where
Length '[] = 'Z
Length (_ ': xs) = 'S (Length xs)
{-------------------------------------------------------------------------------
Singleton instance for type-level symbols
-------------------------------------------------------------------------------}
data instance Sing (n :: Symbol) where
SSymbol :: KnownSymbol n => Sing n
instance KnownSymbol n => SingI (n :: Symbol) where
sing = SSymbol
instance DecidableEquality Symbol where
decideEquality SSymbol SSymbol = sameSymbol Proxy Proxy
{-------------------------------------------------------------------------------
Singleton instance for lists
-------------------------------------------------------------------------------}
data instance Sing (xs :: [k]) where
SN :: Sing '[]
SC :: Sing x -> Sing xs -> Sing (x ': xs)
instance SingI '[] where sing = SN
instance (SingI x, SingI xs) => SingI (x ': xs) where sing = SC sing sing
{-------------------------------------------------------------------------------
General purpose type level functions
-------------------------------------------------------------------------------}
type family Or (a :: Bool) (b :: Bool) where
Or 'True b = 'True
Or a 'True = 'True
Or _ _ = 'False
type family Equal (x :: k) (y :: k) where
Equal x x = 'True
Equal x y = 'False
type family Elem (x :: k) (xs :: [k]) where
Elem x '[] = 'False
Elem x (y ': ys) = Or (Equal x y) (Elem x ys)
-- | Assert type-level predicate
--
-- We cannot define this in terms of a more general @If@ construct, because
-- @ghc@'s type-level language has an undefined reduction order and so we get
-- no short-circuiting.
type family Assert (b :: Bool) (err :: ErrorMessage) :: Constraint where
Assert 'True err = ()
Assert 'False err = TypeError err
{-------------------------------------------------------------------------------
Decidable equality gives a decidable membership check
-------------------------------------------------------------------------------}
data IsElem (x :: k) (xs :: [k]) where
IsElem :: Elem x xs ~ 'True => IsElem x xs
shiftIsElem :: IsElem x ys -> IsElem x (y ': ys)
shiftIsElem IsElem = IsElem
checkIsElem ::
DecidableEquality k
=> Sing (x :: k) -> Sing (xs :: [k]) -> Maybe (IsElem x xs)
checkIsElem _ SN = Nothing
checkIsElem x (SC y ys) = case decideEquality x y of
Just Refl -> Just IsElem
Nothing -> shiftIsElem <$> checkIsElem x ys
{-------------------------------------------------------------------------------
Phantom type parameters
-------------------------------------------------------------------------------}
-- | Functors with phantom arguments
class Phantom (f :: k -> Type) where
-- | Similar to 'Data.Functor.Contravariant.phantom', but without requiring
-- 'Functor' or 'Contravariant'
phantom :: forall a b. f a -> f b
data Poly (f :: k -> Type) = Poly (forall (a :: k). f a)
-- | Commute @Maybe@ and @forall@
maybePoly :: Phantom f => Maybe (f a) -> Maybe (Poly f)
maybePoly = fmap (\v -> Poly (phantom v))