packages feed

deep-transformations-0.2.2: src/Transformation/Deep/TH.hs

-- | This module exports the templates for automatic instance deriving of "Transformation.Deep" type classes. The most
-- common way to use it would be
--
-- > import qualified Transformation.Deep.TH
-- > data MyDataType f' f = ...
-- > $(Transformation.Deep.TH.deriveFunctor ''MyDataType)
--

{-# Language CPP, TemplateHaskell #-}
-- Adapted from https://wiki.haskell.org/A_practical_Template_Haskell_Tutorial

module Transformation.Deep.TH (deriveAll, deriveFunctor, deriveTraversable)
where

import Control.Applicative (liftA2)
import Control.Monad (replicateM)
import Data.Functor.Compose (Compose(getCompose))
import Data.Functor.Const (Const(getConst))
import Data.Maybe (fromMaybe)
import Data.Monoid ((<>))
import Language.Haskell.TH as TH
import Language.Haskell.TH.Syntax (BangType, VarBangType, getQ, putQ)

import qualified Transformation
import qualified Transformation.Deep
import qualified Transformation.Full


data Deriving = Deriving { _constructor :: Name, _variableN :: Name, _variable1 :: Name }

deriveAll :: Name -> Q [Dec]
deriveAll ty = foldr f (pure []) [deriveFunctor, deriveFoldable, deriveTraversable]
   where f derive rest = (<>) <$> derive ty <*> rest

deriveFunctor :: Name -> Q [Dec]
deriveFunctor typeName = do
   t <- varT <$> newName "t"
   (instanceType, cs) <- reifyConstructors typeName
   let deepConstraint ty = conT ''Transformation.Deep.Functor `appT` t `appT` ty
       fullConstraint ty = conT ''Transformation.Full.Functor `appT` t `appT` ty
       baseConstraint ty = conT ''Transformation.At `appT` t `appT` ty
   (constraints, dec) <- genDeepmap baseConstraint deepConstraint fullConstraint instanceType cs
   sequence [instanceD (cxt $ appT (conT ''Transformation.Transformation) t : map pure constraints)
                       (deepConstraint instanceType)
                       [pure dec]]

deriveFoldable :: Name -> Q [Dec]
deriveFoldable typeName = do
   t <- varT <$> newName "t"
   m <- varT <$> newName "m"
   (instanceType, cs) <- reifyConstructors typeName
   let deepConstraint ty = conT ''Transformation.Deep.Foldable `appT` t `appT` ty
       fullConstraint ty = conT ''Transformation.Full.Foldable `appT` t `appT` ty
       baseConstraint ty = conT ''Transformation.At `appT` t `appT` ty
   (constraints, dec) <- genFoldMap baseConstraint deepConstraint fullConstraint instanceType cs
   sequence [instanceD (cxt (appT (conT ''Transformation.Transformation) t :
                             appT (appT equalityT (conT ''Transformation.Codomain `appT` t))
                                  (conT ''Const `appT` m) :
                             appT (conT ''Monoid) m : map pure constraints))
                       (deepConstraint instanceType)
                       [pure dec]]

deriveTraversable :: Name -> Q [Dec]
deriveTraversable typeName = do
   t <- varT <$> newName "t"
   m <- varT <$> newName "m"
   f <- varT <$> newName "f"
   (instanceType, cs) <- reifyConstructors typeName
   let deepConstraint ty = conT ''Transformation.Deep.Traversable `appT` t `appT` ty
       fullConstraint ty = conT ''Transformation.Full.Traversable `appT` t `appT` ty
       baseConstraint ty = conT ''Transformation.At `appT` t `appT` ty
   (constraints, dec) <- genTraverse baseConstraint deepConstraint fullConstraint instanceType cs
   sequence [instanceD (cxt (appT (conT ''Transformation.Transformation) t :
                             appT (appT equalityT (conT ''Transformation.Codomain `appT` t))
                                  (conT ''Compose `appT` m `appT` f) :
                             appT (conT ''Applicative) m : map pure constraints))
                       (deepConstraint instanceType)
                       [pure dec]]

substitute :: Type -> Q Type -> Q Type -> Q Type
substitute resultType = liftA2 substitute'
   where substitute' instanceType argumentType =
            substituteVars (substitutions resultType instanceType) argumentType
         substitutions (AppT t1 (VarT name1)) (AppT t2 (VarT name2)) = (name1, name2) : substitutions t1 t2
         substitutions _t1 _t2 = []
         substituteVars subs (VarT name) = VarT (fromMaybe name $ lookup name subs)
         substituteVars subs (AppT t1 t2) = AppT (substituteVars subs t1) (substituteVars subs t2)
         substituteVars _ t = t

reifyConstructors :: Name -> Q (TypeQ, [Con])
reifyConstructors ty = do
   (TyConI tyCon) <- reify ty
   (tyConName, tyVars, _kind, cs) <- case tyCon of
      DataD _ nm tyVars kind cs _   -> return (nm, tyVars, kind, cs)
      NewtypeD _ nm tyVars kind c _ -> return (nm, tyVars, kind, [c])
      _ -> fail "deriveApply: tyCon may not be a type synonym."

   let reifySynonyms (ConT name) = TH.reify name >>= reifySynonymInfo name
       reifySynonyms (AppT t1 t2) = AppT <$> reifySynonyms t1 <*> reifySynonyms t2
       reifySynonyms t = pure t
       reifySynonymInfo _ (TyConI (TySynD _ [] t)) = reifySynonyms t
       reifySynonymInfo name _ = pure (ConT name)
#if MIN_VERSION_template_haskell(2,17,0)
       reifyTVKindSynonyms (KindedTV v s k) = KindedTV v s <$> reifySynonyms k
#else
       reifyTVKindSynonyms (KindedTV v k) = KindedTV v <$> reifySynonyms k
#endif
       reifyTVKindSynonyms tv = pure tv
   tyVars' <- traverse reifyTVKindSynonyms tyVars
#if MIN_VERSION_template_haskell(2,17,0)
   let (KindedTV tyVar _ (AppT (AppT ArrowT StarT) StarT) :
        KindedTV tyVar' _ (AppT (AppT ArrowT StarT) StarT) : _) = reverse tyVars'
       apply t (PlainTV name _)    = appT t (varT name)
       apply t (KindedTV name _ _) = appT t (varT name)
#else
   let (KindedTV tyVar  (AppT (AppT ArrowT StarT) StarT) :
        KindedTV tyVar' (AppT (AppT ArrowT StarT) StarT) : _) = reverse tyVars'
       apply t (PlainTV name)    = appT t (varT name)
       apply t (KindedTV name _) = appT t (varT name)
#endif
       instanceType           = foldl apply (conT tyConName) (reverse $ drop 2 $ reverse tyVars')

   putQ (Deriving tyConName tyVar' tyVar)
   return (instanceType, cs)

genDeepmap :: (Q Type -> Q Type) -> (Q Type -> Q Type) -> (Q Type -> Q Type) -> Q Type -> [Con] -> Q ([Type], Dec)
genDeepmap baseConstraint deepConstraint fullConstraint instanceType cs = do
   (constraints, clauses) <- unzip <$> mapM (genDeepmapClause baseConstraint deepConstraint
                                                              fullConstraint instanceType) cs
   return (concat constraints, FunD '(Transformation.Deep.<$>) clauses)

genFoldMap :: (Q Type -> Q Type) -> (Q Type -> Q Type) -> (Q Type -> Q Type) -> Q Type -> [Con] -> Q ([Type], Dec)
genFoldMap baseConstraint deepConstraint fullConstraint instanceType cs = do
   (constraints, clauses) <- unzip <$> mapM (genFoldMapClause baseConstraint deepConstraint
                                                              fullConstraint instanceType) cs
   return (concat constraints, FunD 'Transformation.Deep.foldMap clauses)

genTraverse :: (Q Type -> Q Type) -> (Q Type -> Q Type) -> (Q Type -> Q Type) -> Q Type -> [Con] -> Q ([Type], Dec)
genTraverse baseConstraint deepConstraint fullConstraint instanceType cs = do
   (constraints, clauses) <- unzip
     <$> mapM (genTraverseClause genTraverseField baseConstraint deepConstraint fullConstraint instanceType) cs
   return (concat constraints, FunD 'Transformation.Deep.traverse clauses)

genDeepmapClause :: (Q Type -> Q Type) -> (Q Type -> Q Type) -> (Q Type -> Q Type) -> Q Type -> Con
                 -> Q ([Type], Clause)
genDeepmapClause baseConstraint deepConstraint fullConstraint _instanceType (NormalC name fieldTypes) = do
   t          <- newName "t"
   fieldNames <- replicateM (length fieldTypes) (newName "x")
   let pats = [varP t, parensP (conP name $ map varP fieldNames)]
       constraintsAndFields = zipWith newField fieldNames fieldTypes
       newFields = map (snd <$>) constraintsAndFields
       body = normalB $ appsE $ conE name : newFields
       newField :: Name -> BangType -> Q ([Type], Exp)
       newField x (_, fieldType) =
          genDeepmapField (varE t) fieldType baseConstraint deepConstraint fullConstraint (varE x) id
   constraints <- (concat . (fst <$>)) <$> sequence constraintsAndFields
   (,) constraints <$> clause pats body []
genDeepmapClause baseConstraint deepConstraint fullConstraint _instanceType (RecC name fields) = do
   t <- newName "t"
   x <- newName "x"
   let body = normalB $ recConE name $ (snd <$>) <$> constraintsAndFields
       constraintsAndFields = map newNamedField fields
       newNamedField :: VarBangType -> Q ([Type], (Name, Exp))
       newNamedField (fieldName, _, fieldType) =
          ((,) fieldName <$>)
          <$> genDeepmapField (varE t) fieldType baseConstraint deepConstraint fullConstraint
                              (getFieldOf x fieldName) id
   constraints <- (concat . (fst <$>)) <$> sequence constraintsAndFields
   (,) constraints <$> clause [varP t, x `asP` recP name []] body []
genDeepmapClause baseConstraint deepConstraint fullConstraint instanceType
                 (GadtC [name] fieldTypes (AppT (AppT resultType (VarT tyVar')) (VarT tyVar))) =
   do Just (Deriving tyConName _tyVar' _tyVar) <- getQ
      putQ (Deriving tyConName tyVar' tyVar)
      genDeepmapClause (baseConstraint . substitute resultType instanceType)
                       (deepConstraint . substitute resultType instanceType)
                       (fullConstraint . substitute resultType instanceType)
                       instanceType (NormalC name fieldTypes)
genDeepmapClause baseConstraint deepConstraint fullConstraint instanceType
                 (RecGadtC [name] fields (AppT (AppT resultType (VarT tyVar')) (VarT tyVar))) =
   do Just (Deriving tyConName _tyVar' _tyVar) <- getQ
      putQ (Deriving tyConName tyVar' tyVar)
      genDeepmapClause (baseConstraint . substitute resultType instanceType)
                       (deepConstraint . substitute resultType instanceType)
                       (fullConstraint . substitute resultType instanceType)
                       instanceType (RecC name fields)
genDeepmapClause baseConstraint deepConstraint fullConstraint instanceType (ForallC _vars _cxt con) =
   genDeepmapClause baseConstraint deepConstraint fullConstraint instanceType con

genFoldMapClause :: (Q Type -> Q Type) -> (Q Type -> Q Type) -> (Q Type -> Q Type) -> Q Type -> Con
                 -> Q ([Type], Clause)
genFoldMapClause baseConstraint deepConstraint fullConstraint _instanceType (NormalC name fieldTypes) = do
   t          <- newName "t"
   fieldNames <- replicateM (length fieldTypes) (newName "x")
   let pats = [varP t, conP name (map varP fieldNames)]
       constraintsAndFields = zipWith newField fieldNames fieldTypes
       body | null fieldNames = [| mempty |]
            | otherwise = foldr1 append $ (snd <$>) <$> constraintsAndFields
       append a b = [| $(a) <> $(b) |]
       newField :: Name -> BangType -> Q ([Type], Exp)
       newField x (_, fieldType) = genFoldMapField (varE t) fieldType baseConstraint deepConstraint fullConstraint
                                                   (varE x) id
   constraints <- (concat . (fst <$>)) <$> sequence constraintsAndFields
   (,) constraints <$> clause pats (normalB body) []
genFoldMapClause baseConstraint deepConstraint fullConstraint _instanceType (RecC name fields) = do
   t <- newName "t"
   x <- newName "x"
   let body | null fields = [| mempty |]
            | otherwise = foldr1 append $ (snd <$>) <$> constraintsAndFields
       constraintsAndFields = map newField fields
       append a b = [| $(a) <> $(b) |]
       newField :: VarBangType -> Q ([Type], Exp)
       newField (fieldName, _, fieldType) =
          genFoldMapField (varE t) fieldType baseConstraint deepConstraint fullConstraint (getFieldOf x fieldName) id
   constraints <- (concat . (fst <$>)) <$> sequence constraintsAndFields
   (,) constraints <$> clause [varP t, x `asP` recP name []] (normalB body) []
genFoldMapClause baseConstraint deepConstraint fullConstraint instanceType
                 (GadtC [name] fieldTypes (AppT (AppT resultType (VarT tyVar')) (VarT tyVar))) =
   do Just (Deriving tyConName _tyVar' _tyVar) <- getQ
      putQ (Deriving tyConName tyVar' tyVar)
      genFoldMapClause (baseConstraint . substitute resultType instanceType)
                       (deepConstraint . substitute resultType instanceType)
                       (fullConstraint . substitute resultType instanceType)
                       instanceType (NormalC name fieldTypes)
genFoldMapClause baseConstraint deepConstraint fullConstraint instanceType
                 (RecGadtC [name] fields (AppT (AppT resultType (VarT tyVar')) (VarT tyVar))) =
   do Just (Deriving tyConName _tyVar' _tyVar) <- getQ
      putQ (Deriving tyConName tyVar' tyVar)
      genFoldMapClause (baseConstraint . substitute resultType instanceType)
                       (deepConstraint . substitute resultType instanceType)
                       (fullConstraint . substitute resultType instanceType)
                       instanceType (RecC name fields)
genFoldMapClause baseConstraint deepConstraint fullConstraint instanceType (ForallC _vars _cxt con) =
   genFoldMapClause baseConstraint deepConstraint fullConstraint instanceType con

type GenTraverseFieldType = Q Exp -> Type -> (Q Type -> Q Type) -> (Q Type -> Q Type) -> (Q Type -> Q Type)
                            -> Q Exp -> (Q Exp -> Q Exp)
                            -> Q ([Type], Exp)

genTraverseClause :: GenTraverseFieldType -> (Q Type -> Q Type) -> (Q Type -> Q Type) -> (Q Type -> Q Type)
                  -> Q Type -> Con
                  -> Q ([Type], Clause)
genTraverseClause genField baseConstraint deepConstraint fullConstraint _instanceType (NormalC name fieldTypes) =
   do t          <- newName "t"
      fieldNames <- replicateM (length fieldTypes) (newName "x")
      let pats = [varP t, parensP (conP name $ map varP fieldNames)]
          constraintsAndFields = zipWith newField fieldNames fieldTypes
          newFields = map (snd <$>) constraintsAndFields
          body | null fieldTypes = [| pure $(conE name) |]
               | otherwise = fst $ foldl apply (conE name, False) newFields
          apply (a, False) b = ([| $(a) <$> $(b) |], True)
          apply (a, True) b = ([| $(a) <*> $(b) |], True)
          newField :: Name -> BangType -> Q ([Type], Exp)
          newField x (_, fieldType) =
             genField (varE t) fieldType baseConstraint deepConstraint fullConstraint (varE x) id
      constraints <- (concat . (fst <$>)) <$> sequence constraintsAndFields
      (,) constraints <$> clause pats (normalB body) []
genTraverseClause genField baseConstraint deepConstraint fullConstraint _instanceType (RecC name fields) = do
   f <- newName "f"
   x <- newName "x"
   let constraintsAndFields = map newNamedField fields
       body | null fields = [| pure $(conE name) |]
            | otherwise = fst (foldl apply (conE name, False) $ map (snd . snd <$>) constraintsAndFields)
       apply (a, False) b = ([| $(a) <$> $(b) |], True)
       apply (a, True) b = ([| $(a) <*> $(b) |], True)
       newNamedField :: VarBangType -> Q ([Type], (Name, Exp))
       newNamedField (fieldName, _, fieldType) =
          ((,) fieldName <$>)
          <$> genField (varE f) fieldType baseConstraint deepConstraint fullConstraint (getFieldOf x fieldName) id
   constraints <- (concat . (fst <$>)) <$> sequence constraintsAndFields
   (,) constraints <$> clause [varP f, x `asP` recP name []] (normalB body) []
genTraverseClause genField baseConstraint deepConstraint fullConstraint instanceType
                  (GadtC [name] fieldTypes (AppT (AppT resultType (VarT tyVar')) (VarT tyVar))) =
   do Just (Deriving tyConName _tyVar' _tyVar) <- getQ
      putQ (Deriving tyConName tyVar' tyVar)
      genTraverseClause genField
                        (baseConstraint . substitute resultType instanceType)
                        (deepConstraint . substitute resultType instanceType)
                        (fullConstraint . substitute resultType instanceType)
                        instanceType (NormalC name fieldTypes)
genTraverseClause genField baseConstraint deepConstraint fullConstraint instanceType
                  (RecGadtC [name] fields (AppT (AppT resultType (VarT tyVar')) (VarT tyVar))) =
   do Just (Deriving tyConName _tyVar' _tyVar) <- getQ
      putQ (Deriving tyConName tyVar' tyVar)
      genTraverseClause genField
                        (baseConstraint . substitute resultType instanceType)
                        (deepConstraint . substitute resultType instanceType)
                        (fullConstraint . substitute resultType instanceType)
                        instanceType (RecC name fields)
genTraverseClause genField baseConstraint deepConstraint fullConstraint instanceType (ForallC _vars _cxt con) =
   genTraverseClause genField baseConstraint deepConstraint fullConstraint instanceType con

genDeepmapField :: Q Exp -> Type -> (Q Type -> Q Type) -> (Q Type -> Q Type) -> (Q Type -> Q Type)
                -> Q Exp -> (Q Exp -> Q Exp)
                -> Q ([Type], Exp)
genDeepmapField trans fieldType baseConstraint deepConstraint fullConstraint fieldAccess wrap = do
   Just (Deriving _ typeVarN typeVar1) <- getQ
   case fieldType of
     AppT ty (AppT (AppT con v1) v2) | ty == VarT typeVar1, v1 == VarT typeVarN, v2 == VarT typeVarN ->
        (,) <$> ((:[]) <$> fullConstraint (pure con))
            <*> appE (wrap [| ($trans Transformation.Full.<$>) |]) fieldAccess
     AppT ty a | ty == VarT typeVar1 ->
        (,) <$> ((:[]) <$> baseConstraint (pure a))
            <*> (wrap (varE '(Transformation.$) `appE` trans) `appE` fieldAccess)
     AppT (AppT con v1) v2 | v1 == VarT typeVarN, v2 == VarT typeVar1 ->
        (,) <$> ((:[]) <$> deepConstraint (pure con))
            <*> appE (wrap [| Transformation.Deep.fmap $trans |]) fieldAccess
     AppT t1 t2 | t1 /= VarT typeVar1 ->
        genDeepmapField trans t2 baseConstraint deepConstraint fullConstraint fieldAccess (wrap . appE (varE '(<$>)))
     SigT ty _kind -> genDeepmapField trans ty baseConstraint deepConstraint fullConstraint fieldAccess wrap
     ParensT ty -> genDeepmapField trans ty baseConstraint deepConstraint fullConstraint fieldAccess wrap
     _ -> (,) [] <$> fieldAccess

genFoldMapField :: Q Exp -> Type -> (Q Type -> Q Type) -> (Q Type -> Q Type) -> (Q Type -> Q Type)
                -> Q Exp -> (Q Exp -> Q Exp)
                -> Q ([Type], Exp)
genFoldMapField trans fieldType baseConstraint deepConstraint fullConstraint fieldAccess wrap = do
   Just (Deriving _ typeVarN typeVar1) <- getQ
   case fieldType of
     AppT ty (AppT (AppT con v1) v2) | ty == VarT typeVar1, v1 == VarT typeVarN, v2 == VarT typeVarN ->
        (,) <$> ((:[]) <$> fullConstraint (pure con))
            <*> appE (wrap [| Transformation.Full.foldMap $trans |]) fieldAccess
     AppT ty a | ty == VarT typeVar1 ->
        (,) <$> ((:[]) <$> baseConstraint (pure a))
            <*> (wrap (varE '(.) `appE` varE 'getConst `appE` (varE '(Transformation.$) `appE` trans))
                 `appE` fieldAccess)
     AppT (AppT con v1) v2 | v1 == VarT typeVarN, v2 == VarT typeVar1 ->
        (,) <$> ((:[]) <$> deepConstraint (pure con))
            <*> appE (wrap [| Transformation.Deep.foldMap $trans |]) fieldAccess
     AppT t1 t2 | t1 /= VarT typeVar1 ->
        genFoldMapField trans t2 baseConstraint deepConstraint fullConstraint fieldAccess (wrap . appE (varE 'foldMap))
     SigT ty _kind -> genFoldMapField trans ty baseConstraint deepConstraint fullConstraint fieldAccess wrap
     ParensT ty -> genFoldMapField trans ty baseConstraint deepConstraint fullConstraint fieldAccess wrap
     _ -> (,) [] <$> [| mempty |]

genTraverseField :: GenTraverseFieldType
genTraverseField trans fieldType baseConstraint deepConstraint fullConstraint fieldAccess wrap = do
   Just (Deriving _ typeVarN typeVar1) <- getQ
   case fieldType of
     AppT ty (AppT (AppT con v1) v2) | ty == VarT typeVar1, v1 == VarT typeVarN, v2 == VarT typeVarN ->
        (,) <$> ((:[]) <$> fullConstraint (pure con))
            <*> appE (wrap [| Transformation.Full.traverse $trans |]) fieldAccess
     AppT ty a  | ty == VarT typeVar1 ->
        (,) <$> ((:[]) <$> baseConstraint (pure a))
            <*> (wrap (varE '(.) `appE` varE 'getCompose `appE` (varE '(Transformation.$) `appE` trans))
                 `appE` fieldAccess)
     AppT (AppT con v1) v2 | v1 == VarT typeVarN, v2 == VarT typeVar1 ->
        (,) <$> ((:[]) <$> deepConstraint (pure con))
            <*> appE (wrap [| Transformation.Deep.traverse $trans |]) fieldAccess
     AppT t1 t2 | t1 /= VarT typeVar1 -> genTraverseField trans t2 baseConstraint deepConstraint fullConstraint
                                                          fieldAccess (wrap . appE (varE 'traverse))
     SigT ty _kind -> genTraverseField trans ty baseConstraint deepConstraint fullConstraint fieldAccess wrap
     ParensT ty -> genTraverseField trans ty baseConstraint deepConstraint fullConstraint fieldAccess wrap
     _ -> (,) [] <$> [| pure $fieldAccess |]

getFieldOf :: Name -> Name -> Q Exp
getFieldOf = getFieldOfE . varE

getFieldOfE :: Q Exp -> Name -> Q Exp
getFieldOfE record field = do
#if MIN_VERSION_template_haskell(2,19,0)
  dotty <- TH.isExtEnabled TH.OverloadedRecordDot
  if dotty
     then TH.getFieldE record (TH.nameBase field)
     else appE (varE field) record
#else
  appE (varE field) record
#endif