module Data.Morphism.Cata
( CataOptions(..)
, defaultOptions
, makeCata
)
where
import Control.Monad (forM, replicateM)
import Data.Char (toLower)
import Data.Functor ((<$>))
import Language.Haskell.TH
import Language.Haskell.TH.Syntax (mkNameG, NameSpace(TcClsName))
data CataOptions = CataOptions {
cataName :: String
}
defaultOptions :: CataOptions
defaultOptions = CataOptions ""
listName :: Name
listName = mkNameG TcClsName "ghc-prim" "GHC.Types" "[]"
makeFuncT :: Type -> Type -> Type
makeFuncT a = AppT (AppT ArrowT a)
conArgTypes :: Con -> [Type]
conArgTypes (NormalC _ args) = map snd args
conArgTypes (RecC _ args) = map (\(_,_,x) -> x) args
conArgTypes (InfixC arg1 _ arg2) = map snd [arg1, arg2]
conArgTypes (ForallC _ _ c) = conArgTypes c
#if MIN_VERSION_template_haskell(2,11,0)
conArgTypes (GadtC _ args _) = map snd args
conArgTypes (RecGadtC _ args _) = map (\(_,_,x) -> x) args
#endif
conName :: Con -> Name
conName (NormalC n _) = n
conName (RecC n _) = n
conName (InfixC _ n _) = n
conName (ForallC _ _ c) = conName c
#if MIN_VERSION_template_haskell(2,11,0)
conName (GadtC _ _ _) = undefined
conName (RecGadtC _ _ _) = undefined
#endif
typeName :: Type -> Maybe Name
typeName (AppT t _) = typeName t
typeName (ConT n) = Just n
typeName ListT = Just listName
typeName _ = Nothing
conType :: Name -> Name -> Con -> Type
conType inputT resultT c = foldr makeFuncT (VarT resultT) argTypes
where
argTypes = map fixupArgType (conArgTypes c)
fixupArgType t = case typeName t of
Just n -> if n == inputT then VarT resultT else t
Nothing -> t
makeCata :: CataOptions
-> Name
-> Q [Dec]
makeCata opts ty = do
typeInfo <- reify ty
(tyVarBndrs, cons) <- case typeInfo of
#if MIN_VERSION_template_haskell(2,11,0)
TyConI (DataD _ _ tyVarBndrs _ cons _) -> return (tyVarBndrs, cons)
TyConI (NewtypeD _ _ tyVarBndrs _ con _) -> return (tyVarBndrs, [con])
#else
TyConI (DataD _ _ tyVarBndrs cons _) -> return (tyVarBndrs, cons)
TyConI (NewtypeD _ _ tyVarBndrs con _) -> return (tyVarBndrs, [con])
#endif
_ -> fail "makeCata: Expected name of type constructor"
sequence [signature tyVarBndrs cons, funDef cons]
where
signature :: [TyVarBndr] -> [Con] -> Q Dec
signature tyVarBndrs cons = do
let tyVarNames = map tyVarName tyVarBndrs
let typeConType = foldl AppT (ConT ty) (map VarT tyVarNames)
resultTypeName <- newName "a"
let args = map (conType ty resultTypeName) cons ++ [typeConType, VarT resultTypeName]
return (SigD funName (ForallT (PlainTV resultTypeName : tyVarBndrs) [] (foldr1 makeFuncT args)))
funDef :: [Con] -> Q Dec
funDef cons = (FunD funName . (:[])) <$> funImpl cons
funName :: Name
funName = mkName $
if null (cataName opts)
then let (x:xs) = nameBase ty in toLower x : xs
else cataName opts
funImpl :: [Con] -> Q Clause
funImpl cons = do
conArgNames <- replicateM (length cons) (newName "c")
valueArgName <- newName "x"
let funArgs = map VarP (conArgNames ++ [valueArgName])
matches <- forM (zip cons conArgNames) $ \(c, cn) -> do
pat@(ConP _ conPats) <- conToConP c
let patNames = map (\(VarP n) -> n) conPats
let translateArg t arg = case typeName t of
Just n -> if n == ty then foldl AppE (VarE funName) (map VarE (conArgNames ++ [arg])) else VarE arg
Nothing -> VarE arg
let argsWithTypes = zipWith translateArg (conArgTypes c) patNames
let bodyE = foldl AppE (VarE cn) argsWithTypes
return (Match pat (NormalB bodyE) [])
let bodyE = CaseE (VarE valueArgName) matches
return (Clause funArgs (NormalB bodyE) [])
where
conToConP :: Con -> Q Pat
conToConP c = do
argNames <- replicateM (length . conArgTypes $ c) (VarP <$> newName "a")
return (ConP (conName c) argNames)
tyVarName :: TyVarBndr -> Name
tyVarName (PlainTV n) = n
tyVarName (KindedTV n _) = n