{-# LANGUAGE TemplateHaskell #-}
module Generics.SOP.TH
( deriveGeneric
, deriveGenericOnly
, deriveGenericSubst
, deriveGenericOnlySubst
, deriveGenericFunctions
, deriveMetadataValue
, deriveMetadataType
) where
import Control.Monad (join, replicateM, unless)
import Data.List (foldl')
import Data.Maybe (fromMaybe)
import Data.Proxy
import Language.Haskell.TH
import Language.Haskell.TH.Datatype as TH
import Generics.SOP.BasicFunctors
import qualified Generics.SOP.Metadata as SOP
import qualified Generics.SOP.Type.Metadata as SOP.T
import Generics.SOP.NP
import Generics.SOP.NS
import Generics.SOP.Universe
deriveGeneric :: Name -> Q [Dec]
deriveGeneric n =
deriveGenericSubst n varT
deriveGenericOnly :: Name -> Q [Dec]
deriveGenericOnly n =
deriveGenericOnlySubst n varT
deriveGenericSubst :: Name -> (Name -> Q Type) -> Q [Dec]
deriveGenericSubst n f = do
dec <- reifyDatatype n
ds1 <- withDataDec dec (deriveGenericForDataDec f)
ds2 <- withDataDec dec (deriveMetadataForDataDec f)
return (ds1 ++ ds2)
deriveGenericOnlySubst :: Name -> (Name -> Q Type) -> Q [Dec]
deriveGenericOnlySubst n f = do
dec <- reifyDatatype n
withDataDec dec (deriveGenericForDataDec f)
deriveGenericFunctions :: Name -> String -> String -> String -> Q [Dec]
deriveGenericFunctions n codeName fromName toName = do
let codeName' = mkName codeName
let fromName' = mkName fromName
let toName' = mkName toName
dec <- reifyDatatype n
withDataDec dec $ \_variant _cxt name bndrs instTys cons -> do
let codeType = codeFor varT cons
let origType = appTysSubst varT name instTys
let repType = [t| SOP I $(appTyVars varT codeName' bndrs) |]
sequence
[ tySynD codeName' bndrs codeType
, sigD fromName' [t| $origType -> $repType |]
, embedding fromName' cons
, sigD toName' [t| $repType -> $origType |]
, projection toName' cons
]
deriveMetadataValue :: Name -> String -> String -> Q [Dec]
deriveMetadataValue n codeName datatypeInfoName = do
let codeName' = mkName codeName
let datatypeInfoName' = mkName datatypeInfoName
dec <- reifyDatatype n
withDataDec dec $ \variant _cxt name bndrs _instTys cons -> do
sequence [ sigD datatypeInfoName' [t| SOP.DatatypeInfo $(appTyVars varT codeName' bndrs) |]
, funD datatypeInfoName' [clause [] (normalB $ metadata' variant name cons) []]
]
{-# DEPRECATED deriveMetadataValue "Use 'deriveMetadataType' and 'demoteDatatypeInfo' instead." #-}
deriveMetadataType :: Name -> String -> Q [Dec]
deriveMetadataType n datatypeInfoName = do
let datatypeInfoName' = mkName datatypeInfoName
dec <- reifyDatatype n
withDataDec dec $ \ variant _ctx name _bndrs _instTys cons ->
sequence
[ tySynD datatypeInfoName' [] (metadataType' variant name cons) ]
deriveGenericForDataDec ::
(Name -> Q Type) -> DatatypeVariant -> Cxt -> Name -> [TyVarBndr] -> [Type] -> [TH.ConstructorInfo] -> Q [Dec]
deriveGenericForDataDec f _variant _cxt name _bndrs instTys cons = do
let typ = appTysSubst f name instTys
deriveGenericForDataType f typ cons
deriveGenericForDataType :: (Name -> Q Type) -> Q Type -> [TH.ConstructorInfo] -> Q [Dec]
deriveGenericForDataType f typ cons = do
let codeSyn = tySynInstDCompat ''Code Nothing [typ] (codeFor f cons)
inst <- instanceD
(cxt [])
[t| Generic $typ |]
[codeSyn, embedding 'from cons, projection 'to cons]
return [inst]
deriveMetadataForDataDec ::
(Name -> Q Type) -> DatatypeVariant -> Cxt -> Name -> [TyVarBndr] -> [Type] -> [TH.ConstructorInfo] -> Q [Dec]
deriveMetadataForDataDec f variant _cxt name _bndrs instTys cons = do
let typ = appTysSubst f name instTys
deriveMetadataForDataType variant name typ cons
deriveMetadataForDataType :: DatatypeVariant -> Name -> Q Type -> [TH.ConstructorInfo] -> Q [Dec]
deriveMetadataForDataType variant name typ cons = do
md <- instanceD (cxt [])
[t| HasDatatypeInfo $typ |]
[ metadataType typ variant name cons
, funD 'datatypeInfo
[ clause [wildP]
(normalB [| SOP.T.demoteDatatypeInfo (Proxy :: Proxy (DatatypeInfoOf $typ)) |])
[]
]
]
return [md]
codeFor :: (Name -> Q Type) -> [TH.ConstructorInfo] -> Q Type
codeFor f = promotedTypeList . map go
where
go :: TH.ConstructorInfo -> Q Type
go c = do (_, ts) <- conInfo c
promotedTypeListSubst f ts
embedding :: Name -> [TH.ConstructorInfo] -> Q Dec
embedding fromName = funD fromName . go' (\e -> [| Z $e |])
where
go' :: (Q Exp -> Q Exp) -> [TH.ConstructorInfo] -> [Q Clause]
go' _ [] = (:[]) $ do
x <- newName "x"
clause [varP x] (normalB (caseE (varE x) [])) []
go' br cs = go br cs
go :: (Q Exp -> Q Exp) -> [TH.ConstructorInfo] -> [Q Clause]
go _ [] = []
go br (c:cs) = mkClause br c : go (\e -> [| S $(br e) |]) cs
mkClause :: (Q Exp -> Q Exp) -> TH.ConstructorInfo -> Q Clause
mkClause br c = do
(n, ts) <- conInfo c
vars <- replicateM (length ts) (newName "x")
clause [conP n (map varP vars)]
(normalB [| SOP $(br . npE . map (appE (conE 'I) . varE) $ vars) |])
[]
projection :: Name -> [TH.ConstructorInfo] -> Q Dec
projection toName = funD toName . go'
where
go' :: [TH.ConstructorInfo] -> [Q Clause]
go' [] = (:[]) $ do
x <- newName "x"
clause [varP x] (normalB (caseE (varE x) [])) []
go' cs = go id cs
go :: (Q Pat -> Q Pat) -> [TH.ConstructorInfo] -> [Q Clause]
go br [] = [mkUnreachableClause br]
go br (c:cs) = mkClause br c : go (\p -> conP 'S [br p]) cs
mkUnreachableClause :: (Q Pat -> Q Pat) -> Q Clause
mkUnreachableClause br = do
var <- newName "x"
clause [conP 'SOP [br (varP var)]]
(normalB [| $(varE var) `seq` error "inaccessible" |])
[]
mkClause :: (Q Pat -> Q Pat) -> TH.ConstructorInfo -> Q Clause
mkClause br c = do
(n, ts) <- conInfo c
vars <- replicateM (length ts) (newName "x")
clause [conP 'SOP [br . conP 'Z . (:[]) . npP . map (\v -> conP 'I [varP v]) $ vars]]
(normalB . appsE $ conE n : map varE vars)
[]
metadataType :: Q Type -> DatatypeVariant -> Name -> [TH.ConstructorInfo] -> Q Dec
metadataType typ variant typeName cs =
tySynInstDCompat ''DatatypeInfoOf Nothing [typ] (metadataType' variant typeName cs)
metadata' :: DatatypeVariant -> Name -> [TH.ConstructorInfo] -> Q Exp
metadata' dataVariant typeName cs = md
where
md :: Q Exp
md | isNewtypeVariant dataVariant
= [| SOP.Newtype $(stringE (nameModule' typeName))
$(stringE (nameBase typeName))
$(mdCon (head cs))
|]
| otherwise
= [| SOP.ADT $(stringE (nameModule' typeName))
$(stringE (nameBase typeName))
$(npE $ map mdCon cs)
$(popE $ map mdStrictness cs)
|]
mdStrictness :: TH.ConstructorInfo -> Q [Q Exp]
mdStrictness ci@(ConstructorInfo { constructorName = n
, constructorStrictness = bs }) =
checkForGADTs ci $ mdConStrictness n bs
mdConStrictness :: Name -> [FieldStrictness] -> Q [Q Exp]
mdConStrictness n bs = do
dss <- reifyConStrictness n
return (zipWith (\ (FieldStrictness su ss) ds ->
[| SOP.StrictnessInfo
$(mdTHUnpackedness su)
$(mdTHStrictness ss)
$(mdDecidedStrictness ds)
|]) bs dss)
mdCon :: TH.ConstructorInfo -> Q Exp
mdCon ci@(ConstructorInfo { constructorName = n
, constructorVariant = conVariant }) =
checkForGADTs ci $
case conVariant of
NormalConstructor -> [| SOP.Constructor $(stringE (nameBase n)) |]
RecordConstructor ts -> [| SOP.Record $(stringE (nameBase n))
$(npE (map mdField ts))
|]
InfixConstructor -> do
fixity <- reifyFixity n
case fromMaybe defaultFixity fixity of
Fixity f a -> [| SOP.Infix $(stringE (nameBase n))
$(mdAssociativity a)
f
|]
mdField :: Name -> Q Exp
mdField n = [| SOP.FieldInfo $(stringE (nameBase n)) |]
mdTHUnpackedness :: TH.Unpackedness -> Q Exp
mdTHUnpackedness UnspecifiedUnpackedness = [| SOP.NoSourceUnpackedness |]
mdTHUnpackedness NoUnpack = [| SOP.SourceNoUnpack |]
mdTHUnpackedness Unpack = [| SOP.SourceUnpack |]
mdTHStrictness :: TH.Strictness -> Q Exp
mdTHStrictness UnspecifiedStrictness = [| SOP.NoSourceStrictness |]
mdTHStrictness Lazy = [| SOP.SourceLazy |]
mdTHStrictness TH.Strict = [| SOP.SourceStrict |]
mdDecidedStrictness :: DecidedStrictness -> Q Exp
mdDecidedStrictness DecidedLazy = [| SOP.DecidedLazy |]
mdDecidedStrictness DecidedStrict = [| SOP.DecidedStrict |]
mdDecidedStrictness DecidedUnpack = [| SOP.DecidedUnpack |]
mdAssociativity :: FixityDirection -> Q Exp
mdAssociativity InfixL = [| SOP.LeftAssociative |]
mdAssociativity InfixR = [| SOP.RightAssociative |]
mdAssociativity InfixN = [| SOP.NotAssociative |]
metadataType' :: DatatypeVariant -> Name -> [TH.ConstructorInfo] -> Q Type
metadataType' dataVariant typeName cs = md
where
md :: Q Type
md | isNewtypeVariant dataVariant
= [t| 'SOP.T.Newtype $(stringT (nameModule' typeName))
$(stringT (nameBase typeName))
$(mdCon (head cs))
|]
| otherwise
= [t| 'SOP.T.ADT $(stringT (nameModule' typeName))
$(stringT (nameBase typeName))
$(promotedTypeList $ map mdCon cs)
$(promotedTypeListOfList $ map mdStrictness cs)
|]
mdStrictness :: TH.ConstructorInfo -> Q [Q Type]
mdStrictness ci@(ConstructorInfo { constructorName = n
, constructorStrictness = bs }) =
checkForGADTs ci $ mdConStrictness n bs
mdConStrictness :: Name -> [FieldStrictness] -> Q [Q Type]
mdConStrictness n bs = do
dss <- reifyConStrictness n
return (zipWith (\ (FieldStrictness su ss) ds ->
[t| 'SOP.T.StrictnessInfo
$(mdTHUnpackedness su)
$(mdTHStrictness ss)
$(mdDecidedStrictness ds)
|]) bs dss)
mdCon :: TH.ConstructorInfo -> Q Type
mdCon ci@(ConstructorInfo { constructorName = n
, constructorVariant = conVariant }) =
checkForGADTs ci $
case conVariant of
NormalConstructor -> [t| 'SOP.T.Constructor $(stringT (nameBase n)) |]
RecordConstructor ts -> [t| 'SOP.T.Record $(stringT (nameBase n))
$(promotedTypeList (map mdField ts))
|]
InfixConstructor -> do
fixity <- reifyFixity n
case fromMaybe defaultFixity fixity of
Fixity f a -> [t| 'SOP.T.Infix $(stringT (nameBase n))
$(mdAssociativity a)
$(natT f)
|]
mdField :: Name -> Q Type
mdField n = [t| 'SOP.T.FieldInfo $(stringT (nameBase n)) |]
mdTHUnpackedness :: TH.Unpackedness -> Q Type
mdTHUnpackedness UnspecifiedUnpackedness = [t| 'SOP.NoSourceUnpackedness |]
mdTHUnpackedness NoUnpack = [t| 'SOP.SourceNoUnpack |]
mdTHUnpackedness Unpack = [t| 'SOP.SourceUnpack |]
mdTHStrictness :: TH.Strictness -> Q Type
mdTHStrictness UnspecifiedStrictness = [t| 'SOP.NoSourceStrictness |]
mdTHStrictness Lazy = [t| 'SOP.SourceLazy |]
mdTHStrictness TH.Strict = [t| 'SOP.SourceStrict |]
mdDecidedStrictness :: DecidedStrictness -> Q Type
mdDecidedStrictness DecidedLazy = [t| 'SOP.DecidedLazy |]
mdDecidedStrictness DecidedStrict = [t| 'SOP.DecidedStrict |]
mdDecidedStrictness DecidedUnpack = [t| 'SOP.DecidedUnpack |]
mdAssociativity :: FixityDirection -> Q Type
mdAssociativity InfixL = [t| 'SOP.T.LeftAssociative |]
mdAssociativity InfixR = [t| 'SOP.T.RightAssociative |]
mdAssociativity InfixN = [t| 'SOP.T.NotAssociative |]
nameModule' :: Name -> String
nameModule' = fromMaybe "" . nameModule
npE :: [Q Exp] -> Q Exp
npE [] = [| Nil |]
npE (e:es) = [| $e :* $(npE es) |]
popE :: [Q [Q Exp]] -> Q Exp
popE ess =
[| POP $(npE (map (join . fmap npE) ess)) |]
npP :: [Q Pat] -> Q Pat
npP [] = conP 'Nil []
npP (p:ps) = conP '(:*) [p, npP ps]
conInfo :: TH.ConstructorInfo -> Q (Name, [Q Type])
conInfo ci@(ConstructorInfo { constructorName = n
, constructorFields = ts }) =
checkForGADTs ci $ return (n, map return ts)
stringT :: String -> Q Type
stringT = litT . strTyLit
natT :: Int -> Q Type
natT = litT . numTyLit . fromIntegral
promotedTypeList :: [Q Type] -> Q Type
promotedTypeList [] = promotedNilT
promotedTypeList (t:ts) = [t| $promotedConsT $t $(promotedTypeList ts) |]
promotedTypeListOfList :: [Q [Q Type]] -> Q Type
promotedTypeListOfList =
promotedTypeList . map (join . fmap promotedTypeList)
promotedTypeListSubst :: (Name -> Q Type) -> [Q Type] -> Q Type
promotedTypeListSubst _ [] = promotedNilT
promotedTypeListSubst f (t:ts) = [t| $promotedConsT $(t >>= substType f) $(promotedTypeListSubst f ts) |]
appsT :: Name -> [Q Type] -> Q Type
appsT n = foldl' appT (conT n)
appTyVars :: (Name -> Q Type) -> Name -> [TyVarBndr] -> Q Type
appTyVars f n bndrs =
appsT n (map (f . tvName) bndrs)
appTysSubst :: (Name -> Q Type) -> Name -> [Type] -> Q Type
appTysSubst f n args =
appsT n (map (substType f . unSigType) args)
unSigType :: Type -> Type
unSigType (SigT t _) = t
unSigType t = t
substType :: (Name -> Q Type) -> Type -> Q Type
substType f = go
where
go (VarT n) = f n
go (AppT t1 t2) = AppT <$> go t1 <*> go t2
go ListT = return ListT
go (ConT n) = return (ConT n)
go ArrowT = return ArrowT
go (TupleT i) = return (TupleT i)
go t = return t
withDataDec :: TH.DatatypeInfo
-> (DatatypeVariant
-> Cxt
-> Name
-> [TyVarBndr]
-> [Type]
-> [TH.ConstructorInfo]
-> Q a)
-> Q a
withDataDec (TH.DatatypeInfo { datatypeContext = ctxt
, datatypeName = name
, datatypeVars = bndrs
, datatypeInstTypes = instTypes
, datatypeVariant = variant
, datatypeCons = cons }) f =
f variant ctxt name bndrs instTypes cons
checkForGADTs :: TH.ConstructorInfo -> Q a -> Q a
checkForGADTs (ConstructorInfo { constructorVars = exVars
, constructorContext = exCxt }) q = do
unless (null exVars) $ fail "Existentials not supported"
unless (null exCxt) $ fail "GADTs not supported"
q
isNewtypeVariant :: DatatypeVariant -> Bool
isNewtypeVariant Datatype = False
isNewtypeVariant DataInstance = False
isNewtypeVariant Newtype = True
isNewtypeVariant NewtypeInstance = True