hypertypes-0.2.2: src/Hyper/TH/HasPlain.hs
{-# LANGUAGE TemplateHaskell #-}
-- | Generate 'HasHPlain' instances via @TemplateHaskell@
module Hyper.TH.HasPlain
( makeHasHPlain
) where
import qualified Control.Lens as Lens
import qualified Data.Map as Map
import Hyper.Class.HasPlain
import Hyper.TH.Internal.Utils
import Hyper.Type (GetHyperType)
import Hyper.Type.Pure (Pure (..), _Pure)
import Language.Haskell.TH
import qualified Language.Haskell.TH.Datatype as D
import Hyper.Internal.Prelude
-- | Generate a 'HasHPlain' instance
makeHasHPlain :: [Name] -> DecsQ
makeHasHPlain x = traverse makeOne x <&> concat
makeOne :: Name -> Q [Dec]
makeOne typeName = makeTypeInfo typeName >>= makeHasHPlainForType
makeHasHPlainForType :: TypeInfo -> Q [Dec]
makeHasHPlainForType info =
do
ctrs <- traverse (makeCtr (tiName info) (tiHyperParam info)) (tiConstructors info)
let typs = ctrs >>= (^. Lens._4) & filter (not . anHPlainOfCons)
let plains =
typs
>>= \case
ConT hplain `AppT` x | hplain == ''HPlain -> [x]
_ -> []
plainsCtx <- plains <&> AppT (ConT ''HasHPlain) & simplifyContext
showCtx <- typs <&> AppT (ConT ''Show) & simplifyContext
let makeDeriv cls =
standaloneDerivD
(typs <&> AppT (ConT cls) & simplifyContext)
[t|$(conT cls) (HPlain $(pure (tiInstance info)))|]
(:)
<$> instanceD
(pure (showCtx <> plainsCtx))
[t|HasHPlain $(pure (tiInstance info))|]
[ dataInstD (pure []) ''HPlain [pure (tiInstance info)] Nothing (ctrs <&> pure . (^. Lens._1)) []
, funD
'hPlain
[ clause
[]
(normalB [|Lens.iso $(varE fromPlain) $(varE toPlain) . Lens.from _Pure|])
[ funD toPlain (ctrs <&> (^. Lens._2))
, funD fromPlain (ctrs <&> (^. Lens._3))
]
]
]
<*> traverse makeDeriv [''Eq, ''Ord, ''Show]
where
anHPlainOfCons (ConT hplain `AppT` x)
| hplain == ''HPlain =
case unapply x of
(ConT{}, _) -> True
_ -> False
anHPlainOfCons _ = False
toPlain = mkName "toPlain"
fromPlain = mkName "fromPlain"
data FieldInfo = FieldInfo
{ fieldPlainType :: Type
, fieldToPlain :: Q Exp -> Q Exp
, fieldFromPlain :: Q Exp -> Q Exp
}
data FlatInfo = FlatInfo
{ flatIsEmbed :: Bool
, flatCtr :: Name
, flatFields :: [Field]
}
data Field
= NodeField FieldInfo
| FlatFields FlatInfo
makeCtr ::
Name ->
Name ->
(Name, D.ConstructorVariant, [Either Type CtrTypePattern]) ->
Q (Con, ClauseQ, ClauseQ, [Type])
makeCtr top param (cName, _, cFields) =
traverse (forField True) cFields
<&> \xs ->
let plainTypes = xs >>= plainFieldTypes
cVars = [0 :: Int ..] <&> mkName . ('x' :) . show & take (length plainTypes)
in ( plainTypes
<&> (Bang NoSourceUnpackedness NoSourceStrictness,)
& NormalC pcon
, zipWith (>>=) (cVars <&> varE) (xs >>= toPlainFields)
& foldl appE (conE pcon)
& normalB
& (clause [conP cName (toPlainPat cVars xs ^. Lens._1)] ?? [])
, fromPlainFields cVars xs ^. Lens._1
& foldl appE (conE cName)
& normalB
& (clause [conP pcon (cVars <&> varP)] ?? [])
, xs >>= fieldContext
)
where
plainFieldTypes (NodeField x) = [fieldPlainType x]
plainFieldTypes (FlatFields x) = flatFields x >>= plainFieldTypes
toPlainFields (NodeField x) = [fieldToPlain x . pure]
toPlainFields (FlatFields x) = flatFields x >>= toPlainFields
toPlainPat cs [] = ([], cs)
toPlainPat (c : cs) (NodeField{} : xs) = toPlainPat cs xs & Lens._1 %~ (varP c :)
toPlainPat cs0 (FlatFields x : xs) =
toPlainPat cs1 xs & Lens._1 %~ (res :)
where
res
| flatIsEmbed x = embed
| otherwise = conP 'Pure [embed]
embed = conP (flatCtr x) r
(r, cs1) = toPlainPat cs0 (flatFields x)
toPlainPat [] _ = error "out of variables"
fromPlainFields cs [] = ([], cs)
fromPlainFields (c : cs) (NodeField x : xs) =
fromPlainFields cs xs & Lens._1 %~ (fieldFromPlain x (varE c) :)
fromPlainFields cs0 (FlatFields x : xs) =
fromPlainFields cs1 xs & Lens._1 %~ (res :)
where
res
| flatIsEmbed x = embed
| otherwise = [|Pure $embed|]
embed = foldl appE (conE (flatCtr x)) r
(r, cs1) = fromPlainFields cs0 (flatFields x)
fromPlainFields [] _ = error "out of variables"
pcon =
show cName
& reverse
& takeWhile (/= '.')
& reverse
& (<> "P")
& mkName
forField _ (Left t) =
FieldInfo
<$> normalizeType t
?? id
?? id
<&> NodeField
forField isTop (Right x) = forPat isTop x
forPat isTop (Node x) = forGen isTop x
forPat isTop (GenEmbed x) = forGen isTop x
forPat _ (InContainer t p) =
FieldInfo
<$> [t|$(pure t) $(patType p)|]
?? (\x -> [|(hPlain #) <$> $x|])
?? (\x -> [|(^. hPlain) <$> $x|])
<&> NodeField
where
patType (Node x) = [t|HPlain $(pure x)|]
patType (GenEmbed x) = [t|HPlain $(pure x)|]
patType (FlatEmbed x) = [t|HPlain $(pure (tiInstance x))|]
patType (InContainer t' p') = pure t' `appT` patType p'
forPat isTop (FlatEmbed x) =
case tiConstructors x of
[(n, _, xs)] -> traverse (forField False) xs <&> FlatFields . FlatInfo isTop n
_ -> forGen isTop (tiInstance x)
forGen isTop t =
case unapply t of
(ConT c, args) ->
reify c
>>= \case
FamilyI{} -> gen -- Not expanding type families currently
_ ->
do
inner <- D.reifyDatatype c
let subst =
args <> [VarT param]
& zip (D.datatypeVars inner <&> D.tvName)
& Map.fromList
case D.datatypeCons inner of
[x] ->
traverse (matchType top param . D.applySubstitution subst) (D.constructorFields x)
>>= traverse (forField False)
<&> FlatFields . FlatInfo isTop (D.constructorName x)
_ -> gen
_ -> gen
where
gen =
FieldInfo
<$> [t|HPlain $(pure t)|]
?? (\x -> [|hPlain # $x|])
?? (\f -> [|$f ^. hPlain|])
<&> NodeField
normalizeType (ConT g `AppT` VarT v)
| g == ''GetHyperType && v == param = [t|Pure|]
normalizeType (x `AppT` y) = normalizeType x `appT` normalizeType y
normalizeType x = pure x
fieldContext (NodeField x) = [fieldPlainType x]
fieldContext (FlatFields x) = flatFields x >>= fieldContext