th-abstraction-0.1.0.0: src/Language/Haskell/TH/Datatype.hs
{-# Language CPP, DeriveGeneric, DeriveDataTypeable #-}
{-|
Module : Language.Haskell.TH.Datatype
Description : Backwards-compatible interface to reified information about datatypes.
Copyright : Eric Mertens 2017
License : ISC
Maintainer : emertens@gmail.com
This module provides a flattened view of information about data types
and newtypes that can be supported uniformly across multiple verisons
of the template-haskell package.
Sample output for @'reifyDatatype' ''Maybe@
@
'DatatypeInfo'
{ 'datatypeContext' = []
, 'datatypeName' = GHC.Base.Maybe
, 'datatypeVars' = [ 'VarT' a_3530822107858468866 ]
, 'datatypeVariant' = 'Datatype'
, 'datatypeCons' =
[ 'ConstructorInfo'
{ 'constructorName' = GHC.Base.Nothing
, 'constructorVars' = []
, 'constructorContext' = []
, 'constructorFields' = []
, 'constructorVariant' = 'NormalConstructor'
}
, 'ConstructorInfo'
{ 'constructorName' = GHC.Base.Just
, 'constructorVars' = []
, 'constructorContext' = []
, 'constructorFields' = [ 'VarT' a_3530822107858468866 ]
, 'constructorVariant' = 'NormalConstructor'
}
]
}
@
Datatypes declared with GADT syntax are normalized to constructors with existentially
quantified type variables and equality constraints.
-}
module Language.Haskell.TH.Datatype
(
-- * Types
DatatypeInfo(..)
, ConstructorInfo(..)
, DatatypeVariant(..)
, ConstructorVariant(..)
-- * Normalization functions
, reifyDatatype
, normalizeInfo
, normalizeDec
, normalizeCon
-- * Type variable manipulation
, TypeSubstitution(..)
, quantifyType
, freshenFreeVariables
-- * 'Pred' functions
, equalPred
, classPred
-- * Convenience functions
, resolveTypeSynonyms
, unifyTypes
, tvName
, datatypeType
) where
import Data.Data (Typeable, Data)
import Data.Foldable (foldMap, foldl')
import Data.List (union, (\\))
import Data.Map (Map)
import qualified Data.Map as Map
import Control.Monad (foldM)
import GHC.Generics (Generic)
import Language.Haskell.TH
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative (Applicative(..), (<$>))
import Data.Traversable (traverse, sequenceA)
#endif
-- | Normalized information about newtypes and data types.
data DatatypeInfo = DatatypeInfo
{ datatypeContext :: Cxt -- ^ Data type context (deprecated)
, datatypeName :: Name -- ^ Type constructor
, datatypeVars :: [Type] -- ^ Type parameters
, datatypeVariant :: DatatypeVariant -- ^ Extra information
, datatypeCons :: [ConstructorInfo] -- ^ Normalize constructor information
}
deriving (Show, Eq, Typeable, Data, Generic)
-- | Possible variants of data type declarations.
data DatatypeVariant
= Datatype -- ^ Type declared with @data@
| Newtype -- ^ Type declared with @newtype@
| DataInstance -- ^ Type declared with @data instance@
| NewtypeInstance -- ^ Type declared with @newtype instance@
deriving (Show, Read, Eq, Ord, Typeable, Data, Generic)
-- | Normalized information about constructors associated with newtypes and
-- data types.
data ConstructorInfo = ConstructorInfo
{ constructorName :: Name -- ^ Constructor name
, constructorVars :: [TyVarBndr] -- ^ Constructor type parameters
, constructorContext :: Cxt -- ^ Constructor constraints
, constructorFields :: [Type] -- ^ Constructor fields
, constructorVariant :: ConstructorVariant -- ^ Extra information
}
deriving (Show, Eq, Typeable, Data, Generic)
-- | Possible variants of data constructors.
data ConstructorVariant
= NormalConstructor -- ^ Constructor without field names
| RecordConstructor [Name] -- ^ Constructor with field names
deriving (Show, Eq, Ord, Typeable, Data, Generic)
-- | Construct a Type using the datatype's type constructor and type
-- parameteters.
datatypeType :: DatatypeInfo -> Type
datatypeType di
= foldl AppT (ConT (datatypeName di))
$ datatypeVars di
-- | Compute a normalized view of the metadata about a data type or newtype
-- given a type constructor.
reifyDatatype ::
Name {- ^ type constructor -} ->
Q DatatypeInfo
reifyDatatype n = normalizeInfo =<< reify n
-- | Normalize 'Info' for a newtype or datatype into a 'DatatypeInfo'.
-- Fail in 'Q' otherwise.
normalizeInfo :: Info -> Q DatatypeInfo
normalizeInfo (TyConI dec) = normalizeDec dec
normalizeInfo _ = fail "reifyDatatype: Expected a type constructor"
-- | Normalize 'Dec' for a newtype or datatype into a 'DatatypeInfo'.
-- Fail in 'Q' otherwise.
normalizeDec :: Dec -> Q DatatypeInfo
#if MIN_VERSION_template_haskell(2,12,0)
normalizeDec (NewtypeD context name tyvars _kind con _derives) =
normalizeDec' context name (bndrParams tyvars) [con] Newtype
normalizeDec (DataD context name tyvars _kind cons _derives) =
normalizeDec' context name (bndrParams tyvars) cons Datatype
normalizeDec (NewtypeInstD context name params _kind con _derives) =
normalizeDec' context name params [con] NewtypeInstance
normalizeDec (DataInstD context name params _kind cons _derives) =
normalizeDec' context name params cons DataInstance
#elif MIN_VERSION_template_haskell(2,11,0)
normalizeDec (NewtypeD context name tyvars _kind con _derives) =
normalizeDec' context name (bndrParams tyvars) [con] Newtype
normalizeDec (DataD context name tyvars _kind cons _derives) =
normalizeDec' context name (bndrParams tyvars) cons Datatype
normalizeDec (NewtypeInstD context name params _kind con _derives) =
normalizeDec' context name params [con] NewtypeInstance
normalizeDec (DataInstD context name params _kind cons _derives) =
normalizeDec' context name params cons DataInstance
#else
normalizeDec (NewtypeD context name tyvars con _derives) =
normalizeDec' context name (bndrParams tyvars) [con] Newtype
normalizeDec (DataD context name tyvars cons _derives) =
normalizeDec' context name (bndrParams tyvars) cons Datatype
normalizeDec (NewtypeInstD context name params con _derives) =
normalizeDec' context name params [con] NewtypeInstance
normalizeDec (DataInstD context name params cons _derives) =
normalizeDec' context name params cons DataInstance
#endif
normalizeDec _ = fail "reifyDatatype: DataD or NewtypeD required"
bndrParams :: [TyVarBndr] -> [Type]
bndrParams = map (VarT . tvName)
normalizeDec' ::
Cxt {- ^ Datatype context -} ->
Name {- ^ Type constructor -} ->
[Type] {- ^ Type parameters -} ->
[Con] {- ^ Constructors -} ->
DatatypeVariant {- ^ Extra information -} ->
Q DatatypeInfo
normalizeDec' context name params cons variant =
do let vs = freeVariables params
cons' <- concat <$> traverse (normalizeCon name vs) cons
pure DatatypeInfo
{ datatypeContext = context
, datatypeName = name
, datatypeVars = params
, datatypeCons = cons'
, datatypeVariant = variant
}
-- | Normalize a 'Con' into a 'ConstructorInfo'. This requires knowledge of
-- the type and parameters of the constructor as extracted from the outer
-- 'Dec'.
normalizeCon ::
Name {- ^ Type constructor -} ->
[Name] {- ^ Type parameters -} ->
Con {- ^ Constructor -} ->
Q [ConstructorInfo]
normalizeCon typename vars = go [] []
where
go tyvars context c =
case c of
NormalC n xs ->
pure [ConstructorInfo n tyvars context (map snd xs) NormalConstructor]
InfixC l n r ->
pure [ConstructorInfo n tyvars context [snd l,snd r] NormalConstructor]
RecC n xs ->
let fns = takeFieldNames xs in
pure [ConstructorInfo n tyvars context
(takeFieldTypes xs) (RecordConstructor fns)]
ForallC tyvars' context' c' ->
go (tyvars'++tyvars) (context'++context) c'
#if MIN_VERSION_template_haskell(2,11,0)
GadtC ns xs innerType ->
gadtCase ns innerType (map snd xs) NormalConstructor
RecGadtC ns xs innerType ->
let fns = takeFieldNames xs in
gadtCase ns innerType (takeFieldTypes xs) (RecordConstructor fns)
where
gadtCase = normalizeGadtC typename vars tyvars context
normalizeGadtC ::
Name {- ^ Type constructor -} ->
[Name] {- ^ Type parameters -} ->
[TyVarBndr] {- ^ Constructor parameters -} ->
Cxt {- ^ Constructor context -} ->
[Name] {- ^ Constructor names -} ->
Type {- ^ Declared type of constructor -} ->
[Type] {- ^ Constructor field types -} ->
ConstructorVariant {- ^ Constructor variant -} ->
Q [ConstructorInfo]
normalizeGadtC typename vars tyvars context names innerType fields variant =
do innerType' <- resolveTypeSynonyms innerType
case decomposeType innerType' of
ConT innerTyCon :| ts | typename == innerTyCon ->
let (substName, context1) = mergeArguments vars ts
subst = VarT <$> substName
tyvars' = [ tv | tv <- tyvars, Map.notMember (tvName tv) subst ]
context2 = applySubstitution subst (context1 ++ context)
fields' = applySubstitution subst fields
in pure [ConstructorInfo name tyvars' context2 fields' variant
| name <- names]
_ -> fail "normalizeGadtC: Expected type constructor application"
mergeArguments :: [Name] -> [Type] -> (Map Name Name, Cxt)
mergeArguments ns ts = foldl' aux (Map.empty, []) (zip ns ts)
where
aux (subst, context) (n,p) =
case p of
VarT m | Map.notMember m subst -> (Map.insert m n subst, context)
_ -> (subst, EqualityT `AppT` VarT n `AppT` p : context)
#endif
-- | Expand all of the type synonyms in a type.
resolveTypeSynonyms :: Type -> Q Type
resolveTypeSynonyms t =
let f :| xs = decomposeType t
notTypeSynCase = foldl AppT f <$> traverse resolveTypeSynonyms xs
in case f of
ConT n ->
do info <- reify n
case info of
TyConI (TySynD _ synvars def) ->
let argNames = map tvName synvars
(args,rest) = splitAt (length argNames) xs
subst = Map.fromList (zip argNames args)
t' = foldl AppT (applySubstitution subst def) rest
in resolveTypeSynonyms t'
_ -> notTypeSynCase
_ -> notTypeSynCase
-- | Decompose a type into a list of it's outermost applications. This process
-- forgets about infix application and explicit parentheses.
--
-- > t ~= foldl1 AppT (decomposeType t)
decomposeType :: Type -> NonEmpty Type
decomposeType = reverseNonEmpty . go
where
go (AppT f x ) = x <| go f
#if MIN_VERSION_template_haskell(2,11,0)
go (InfixT l f r) = ConT f :| [l,r]
go (UInfixT l f r) = ConT f :| [l,r]
go (ParensT t ) = decomposeType t
#endif
go t = t :| []
-- | Extract the type variable name from a 'TyVarBndr' ignoring the
-- kind signature if one exists.
tvName :: TyVarBndr -> Name
tvName (PlainTV name ) = name
tvName (KindedTV name _) = name
takeFieldNames :: [(Name,a,b)] -> [Name]
takeFieldNames xs = [a | (a,_,_) <- xs]
takeFieldTypes :: [(a,b,Type)] -> [Type]
takeFieldTypes xs = [a | (_,_,a) <- xs]
------------------------------------------------------------------------
-- | Add universal quantifier for all free variables in the type. This is
-- useful when constructing a type signature for a declaration.
-- This code is careful to ensure that the order of the variables quantified
-- is determined by their order of appearance in the type singnature. (In
-- contrast with being dependent upon the Ord instance for 'Name')
--
quantifyType :: Type -> Type
quantifyType t
| null vs = t
| otherwise = ForallT (PlainTV <$> vs) [] t
where
vs = freeVariables t
-- | Substitute all of the free variables in a type with fresh ones
freshenFreeVariables :: Type -> Q Type
freshenFreeVariables t =
do let xs = [ (n, VarT <$> newName (nameBase n)) | n <- freeVariables t]
subst <- sequenceA (Map.fromList xs)
return (applySubstitution subst t)
-- | Class for types that support type variable substitution.
class TypeSubstitution a where
-- | Apply a type variable substitution
applySubstitution :: Map Name Type -> a -> a
-- | Compute the free type variables
freeVariables :: a -> [Name]
instance TypeSubstitution a => TypeSubstitution [a] where
freeVariables = foldMap freeVariables
applySubstitution = fmap . applySubstitution
instance TypeSubstitution Type where
applySubstitution subst = go
where
go (ForallT tvs context t) =
let subst' = foldl' (flip Map.delete) subst (map tvName tvs) in
ForallT tvs (applySubstitution subst' context)
(applySubstitution subst' t)
go (AppT f x) = AppT (go f) (go x)
go (SigT t k) = SigT (go t) (applySubstitution subst k) -- k could be Kind
go (VarT v) = Map.findWithDefault (VarT v) v subst
#if MIN_VERSION_template_haskell(2,11,0)
go (InfixT l c r) = InfixT (go l) c (go r)
go (UInfixT l c r) = UInfixT (go l) c (go r)
go (ParensT t) = ParensT (go t)
#endif
go t = t
freeVariables t =
case t of
ForallT tvs context t' ->
(freeVariables context `union` freeVariables t')
\\ map tvName tvs
AppT f x -> freeVariables f `union` freeVariables x
SigT t' k -> freeVariables t' `union` freeVariables k
VarT v -> [v]
#if MIN_VERSION_template_haskell(2,11,0)
InfixT l _ r -> freeVariables l `union` freeVariables r
UInfixT l _ r -> freeVariables l `union` freeVariables r
ParensT t' -> freeVariables t'
#endif
_ -> []
instance TypeSubstitution ConstructorInfo where
freeVariables ci =
(freeVariables (constructorContext ci) `union`
freeVariables (constructorFields ci))
\\ (tvName <$> constructorVars ci)
applySubstitution subst ci =
let subst' = foldl' (flip Map.delete) subst (map tvName (constructorVars ci)) in
ci { constructorContext = applySubstitution subst' (constructorContext ci)
, constructorFields = applySubstitution subst' (constructorFields ci)
}
-- Pred became a type synonym for Type
#if !MIN_VERSION_template_haskell(2,10,0)
instance TypeSubstitution Pred where
freeVariables (ClassP _ xs) = freeVariables xs
freeVariables (EqualP x y) = freeVariables x `union` freeVariables y
applySubstitution p (ClassP n xs) = ClassP n (applySubstitution p xs)
applySubstitution p (EqualP x y) = EqualP (applySubstitution p x)
(applySubstitution p y)
#endif
-- Kind became a type synonym for Type. Previously there were no kind variables
#if !MIN_VERSION_template_haskell(2,8,0)
instance TypeSubstitution Kind where
freeVariables _ = []
applySubstitution _ k = k
#endif
------------------------------------------------------------------------
combineSubstitutions :: Map Name Type -> Map Name Type -> Map Name Type
combineSubstitutions x y = Map.union (fmap (applySubstitution y) x) y
-- | Compute the type variable substitution that unifies a list of types,
-- or fail in 'Q'.
unifyTypes :: [Type] -> Q (Map Name Type)
unifyTypes [] = pure Map.empty
unifyTypes (t:ts) =
do t':ts' <- traverse resolveTypeSynonyms (t:ts)
let aux sub u =
do sub' <- unify' (applySubstitution sub t')
(applySubstitution sub u)
return (combineSubstitutions sub sub')
case foldM aux Map.empty ts' of
Right m -> return m
Left (x,y) ->
fail $ showString "Unable to unify types "
. showsPrec 11 x
. showString " and "
. showsPrec 11 y
$ ""
unify' :: Type -> Type -> Either (Type,Type) (Map Name Type)
unify' (VarT n) (VarT m) | n == m = pure Map.empty
unify' (VarT n) t | n `elem` freeVariables t = Left (VarT n, t)
| otherwise = pure (Map.singleton n t)
unify' t (VarT n) | n `elem` freeVariables t = Left (VarT n, t)
| otherwise = pure (Map.singleton n t)
unify' (ConT n) (ConT m) | n == m = pure Map.empty
unify' (AppT f1 x1) (AppT f2 x2) =
do sub1 <- unify' f1 f2
sub2 <- unify' (applySubstitution sub1 x1) (applySubstitution sub1 x2)
return (combineSubstitutions sub1 sub2)
unify' (TupleT n) (TupleT m) | n == m = pure Map.empty
unify' t u = Left (t,u)
-- | Construct an equality constraint. The implementation of 'Pred' varies
-- across versions of Template Haskell.
equalPred :: Type -> Type -> Pred
equalPred x y =
#if MIN_VERSION_template_haskell(2,10,0)
AppT (AppT EqualityT x) y
#else
EqualP x y
#endif
-- | Construct a typeclass constraint. The implementation of 'Pred' varies
-- across versions of Template Haskell.
classPred :: Name {- ^ class -} -> [Type] {- ^ parameters -} -> Pred
classPred =
#if MIN_VERSION_template_haskell(2,10,0)
foldl AppT . ConT
#else
ClassP
#endif
------------------------------------------------------------------------
-- NonEmpty didn't move into base into recently. Reimplementing it locally
-- saves dependencies for supporting older GHCs
data NonEmpty a = a :| [a]
(<|) :: a -> NonEmpty a -> NonEmpty a
x <| (y :| ys) = x :| (y : ys)
reverseNonEmpty :: NonEmpty a -> NonEmpty a
reverseNonEmpty (x :| xs) = y :| ys
where y:ys = reverse (x:xs)