partial-isomorphisms-0.2.3.0: src/Control/Isomorphism/Partial/TH.hs
{-# LANGUAGE TemplateHaskell, DataKinds #-}
module Control.Isomorphism.Partial.TH
( constructorIso
, defineIsomorphisms
) where
import Control.Monad
import Data.Char (toLower)
import Data.List (find)
import Language.Haskell.TH
import Control.Isomorphism.Partial.Unsafe (Iso (Iso))
gadtError :: a
gadtError = error "Control.Isomorphism.Partial.TH: GADTs currently not supported."
{-# NOINLINE gadtError #-}
-- | Extract the name of a constructor, e.g. ":" or "Just".
conName :: Con -> Name
conName (NormalC name _) = name
conName (RecC name _) = name
conName (InfixC _ name _) = name
conName (ForallC _ _ con) = conName con
conName (GadtC _ _ _) = gadtError
conName (RecGadtC _ _ _) = gadtError
-- | Extract the types of the constructor's fields.
conFields :: Con -> [Type]
conFields (NormalC _ fields) = map (\(_, t) -> t) fields
conFields (RecC _ fields) = map (\(_, _, t) -> t) fields
conFields (InfixC lhs _ rhs) = map (\(_, t) -> t) [lhs, rhs]
conFields (ForallC _ _ con) = conFields con
conFields (GadtC _ _ _) = gadtError
conFields (RecGadtC _ _ _) = gadtError
-- Data dec information
data DecInfo flag = DecInfo Type [TyVarBndr flag] [Con]
-- | Extract data or newtype declaration information
decInfo :: Dec -> Q (DecInfo ())
decInfo (DataD _ name tyVars _ cs _) = return $ DecInfo (ConT name) tyVars cs
decInfo (NewtypeD _ name tyVars _ c _) = return $ DecInfo (ConT name) tyVars [c]
decInfo _ = fail "partial isomorphisms can only be derived for constructors of data type or newtype declarations."
-- | Convert tyVarBndr to type
tyVarBndrToType :: TyVarBndr () -> Type
tyVarBndrToType (PlainTV n _) = VarT n
tyVarBndrToType (KindedTV n _ k) = SigT (VarT n) k
-- | Create Iso type for specified type and conctructor fields (Iso (a, b) (CustomType a b c))
isoType :: Type -> [TyVarBndr ()] -> [Type] -> Q Type
isoType typ tyVarBndrs fields = do
isoCon <- [t| Iso |]
return $ ForallT (map specified tyVarBndrs) [] $ isoCon `AppT` (isoArgs fields) `AppT` (applyAll typ $ map tyVarBndrToType tyVarBndrs)
where
specified (PlainTV name _) = PlainTV name SpecifiedSpec
specified (KindedTV name _ kind) = KindedTV name SpecifiedSpec kind
isoArgs :: [Type] -> Type
isoArgs [] = TupleT 0
isoArgs [x] = x
isoArgs (x:xs) = AppT (AppT (TupleT 2) x) (isoArgs xs)
-- | Apply all types to supplied type
applyAll :: Type -> [Type] -> Type
applyAll = foldl AppT
-- | Construct a partial isomorphism expression for a constructor,
-- given the constructor's name.
constructorIso :: Name -> ExpQ
constructorIso name = do
DataConI n _ d <- reify name
TyConI dec <- reify d
DecInfo _ _ cs <- decInfo dec
let Just con = find (\c -> n == conName c) cs
isoFromCon (wildcard cs) con
wildcard :: [Con] -> [MatchQ]
wildcard cs
= if length cs > 1
then [match (wildP) (normalB [| Nothing |]) []]
else []
-- | Converts a constructor name (starting with an upper-case
-- letter) into a function name (starting with a lower-case
-- letter).
rename :: Name -> Name
rename n
= mkName (toLower c : cs) where c : cs = nameBase n
defineIsomorphisms :: Name -> Q [Dec]
defineIsomorphisms d = do
TyConI dec <- reify d
DecInfo typ tyVarBndrs cs <- decInfo dec
join `fmap` mapM (\a -> defFromCon (wildcard cs) typ tyVarBndrs a) cs
-- | Constructs a partial isomorphism definition for a
-- constructor, given information about the constructor.
-- The name of the partial isomorphisms is constructed by
-- spelling the constructor name with an initial lower-case
-- letter.
defFromCon :: [MatchQ] -> Type -> [TyVarBndr ()] -> Con -> DecsQ
defFromCon matches t tyVarBndrs con = do
let funName = rename $ conName con
sig <- SigD funName `fmap` isoType t tyVarBndrs (conFields con)
fun <- funD funName [ clause [] (normalB (isoFromCon matches con)) [] ]
return [sig, fun]
-- | Constructs a partial isomorphism expression for a
-- constructor, given information about the constructor.
isoFromCon :: [MatchQ] -> Con -> ExpQ
isoFromCon matches con = do
let c = conName con
let fs = conFields con
let n = length fs
(ps, vs) <- genPE n
v <- newName "x"
let f = lamE [nested tupP ps]
[| Just $(foldl appE (conE c) vs) |]
let g = lamE [varP v]
(caseE (varE v) $
[ match (conP c ps)
(normalB [| Just $(nested tupE vs) |]) []
] ++ matches)
[| Iso $f $g |]
genPE :: Int -> Q ([PatQ], [ExpQ])
genPE n = do
ids <- replicateM n (newName "x")
return (map varP ids, map varE ids)
nested :: ([t] -> t) -> [t] -> t
nested tup [] = tup []
nested _ [x] = x
nested tup (x:xs) = tup [x, nested tup xs]