{-# LANGUAGE CPP #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE BangPatterns #-}
#ifndef MIN_VERSION_template_haskell
#define MIN_VERSION_template_haskell(x,y,z) 1
#endif
module Data.Bifunctor.TH (
deriveBifunctor
, makeBimap
, deriveBifoldable
, makeBifold
, makeBifoldMap
, makeBifoldr
, makeBifoldl
, deriveBitraversable
, makeBitraverse
, makeBisequenceA
, makeBimapM
, makeBisequence
) where
import Control.Monad (guard)
import Data.Bifunctor.TH.Internal
import Data.List
import Data.Maybe
#if __GLASGOW_HASKELL__ < 710 && MIN_VERSION_template_haskell(2,8,0)
import qualified Data.Set as Set
#endif
import Language.Haskell.TH.Lib
import Language.Haskell.TH.Ppr
import Language.Haskell.TH.Syntax
deriveBifunctor :: Name -> Q [Dec]
deriveBifunctor = deriveBiClass Bifunctor
makeBimap :: Name -> Q Exp
makeBimap = makeBiFun Bimap
deriveBifoldable :: Name -> Q [Dec]
deriveBifoldable = deriveBiClass Bifoldable
makeBifold :: Name -> Q Exp
makeBifold name = appsE [ makeBifoldMap name
, varE idValName
, varE idValName
]
makeBifoldMap :: Name -> Q Exp
makeBifoldMap = makeBiFun BifoldMap
makeBifoldr :: Name -> Q Exp
makeBifoldr = makeBiFun Bifoldr
makeBifoldl :: Name -> Q Exp
makeBifoldl name = do
f <- newName "f"
g <- newName "g"
z <- newName "z"
t <- newName "t"
lamE [varP f, varP g, varP z, varP t] $
appsE [ varE appEndoValName
, appsE [ varE getDualValName
, appsE [ makeBifoldMap name, foldFun f, foldFun g, varE t]
]
, varE z
]
where
foldFun :: Name -> Q Exp
foldFun n = infixApp (conE dualDataName)
(varE composeValName)
(infixApp (conE endoDataName)
(varE composeValName)
(varE flipValName `appE` varE n)
)
deriveBitraversable :: Name -> Q [Dec]
deriveBitraversable = deriveBiClass Bitraversable
makeBitraverse :: Name -> Q Exp
makeBitraverse = makeBiFun Bitraverse
makeBisequenceA :: Name -> Q Exp
makeBisequenceA name = appsE [ makeBitraverse name
, varE idValName
, varE idValName
]
makeBimapM :: Name -> Q Exp
makeBimapM name = do
f <- newName "f"
g <- newName "g"
lamE [varP f, varP g] . infixApp (varE unwrapMonadValName) (varE composeValName) $
appsE [makeBitraverse name, wrapMonadExp f, wrapMonadExp g]
where
wrapMonadExp :: Name -> Q Exp
wrapMonadExp n = infixApp (conE wrapMonadDataName) (varE composeValName) (varE n)
makeBisequence :: Name -> Q Exp
makeBisequence name = appsE [ makeBimapM name
, varE idValName
, varE idValName
]
deriveBiClass :: BiClass -> Name -> Q [Dec]
deriveBiClass biClass name = withType name fromCons where
fromCons :: Name -> Cxt -> [TyVarBndr] -> [Con] -> Maybe [Type] -> Q [Dec]
fromCons name' ctxt tvbs cons mbTys = (:[]) `fmap`
instanceD (return instanceCxt)
(return instanceType)
(biFunDecs biClass droppedNbs cons)
where
instanceCxt :: Cxt
instanceType :: Type
droppedNbs :: [NameBase]
(instanceCxt, instanceType, droppedNbs) =
buildTypeInstance biClass name' ctxt tvbs mbTys
biFunDecs :: BiClass -> [NameBase] -> [Con] -> [Q Dec]
biFunDecs biClass nbs cons = map makeFunD $ biClassToFuns biClass where
makeFunD :: BiFun -> Q Dec
makeFunD biFun =
funD (biFunName biFun)
[ clause []
(normalB $ makeBiFunForCons biFun nbs cons)
[]
]
makeBiFun :: BiFun -> Name -> Q Exp
makeBiFun biFun name = withType name fromCons where
fromCons :: Name -> Cxt -> [TyVarBndr] -> [Con] -> Maybe [Type] -> Q Exp
fromCons name' ctxt tvbs cons mbTys =
let !nbs = thd3 $ buildTypeInstance (biFunToClass biFun) name' ctxt tvbs mbTys
in makeBiFunForCons biFun nbs cons
makeBiFunForCons :: BiFun -> [NameBase] -> [Con] -> Q Exp
makeBiFunForCons biFun nbs cons = do
argNames <- mapM newName $ catMaybes [ Just "f"
, Just "g"
, guard (biFun == Bifoldr) >> Just "z"
, Just "value"
]
let (maps,others) = splitAt 2 argNames
z = head others
value = last others
tvis = zip nbs maps
lamE (map varP argNames)
. appsE
$ [ varE $ biFunConstName biFun
, if null cons
then appE (varE errorValName)
(stringE $ "Void " ++ nameBase (biFunName biFun))
else caseE (varE value)
(map (makeBiFunForCon biFun z tvis) cons)
] ++ map varE argNames
makeBiFunForCon :: BiFun -> Name -> [TyVarInfo] -> Con -> Q Match
makeBiFunForCon biFun z tvis (NormalC conName tys) = do
args <- newNameList "arg" $ length tys
let argTys = map snd tys
makeBiFunForArgs biFun z tvis conName argTys args
makeBiFunForCon biFun z tvis (RecC conName tys) = do
args <- newNameList "arg" $ length tys
let argTys = map thd3 tys
makeBiFunForArgs biFun z tvis conName argTys args
makeBiFunForCon biFun z tvis (InfixC (_, argTyL) conName (_, argTyR)) = do
argL <- newName "argL"
argR <- newName "argR"
makeBiFunForArgs biFun z tvis conName [argTyL, argTyR] [argL, argR]
makeBiFunForCon biFun z tvis (ForallC tvbs faCxt con)
| any (`predMentionsNameBase` map fst tvis) faCxt && not (allowExQuant (biFunToClass biFun))
= existentialContextError (constructorName con)
| otherwise = makeBiFunForCon biFun z (removeForalled tvbs tvis) con
makeBiFunForArgs :: BiFun
-> Name
-> [TyVarInfo]
-> Name
-> [Type]
-> [Name]
-> Q Match
makeBiFunForArgs biFun z tvis conName tys args =
match (conP conName $ map varP args)
(normalB $ biFunCombine biFun conName z mappedArgs)
[]
where
mappedArgs :: [Q Exp]
mappedArgs = zipWith (makeBiFunForArg biFun tvis conName) tys args
makeBiFunForArg :: BiFun
-> [TyVarInfo]
-> Name
-> Type
-> Name
-> Q Exp
makeBiFunForArg biFun tvis conName ty tyExpName = do
ty' <- expandSyn ty
makeBiFunForArg' biFun tvis conName ty' tyExpName
makeBiFunForArg' :: BiFun
-> [TyVarInfo]
-> Name
-> Type
-> Name
-> Q Exp
makeBiFunForArg' biFun tvis conName ty tyExpName =
makeBiFunForType biFun tvis conName True ty `appE` varE tyExpName
makeBiFunForType :: BiFun
-> [TyVarInfo]
-> Name
-> Bool
-> Type
-> Q Exp
makeBiFunForType biFun tvis conName covariant (VarT tyName) =
case lookup (NameBase tyName) tvis of
Just mapName -> varE $ if covariant
then mapName
else contravarianceError conName
Nothing -> biFunTriv biFun
makeBiFunForType biFun tvis conName covariant (SigT ty _) =
makeBiFunForType biFun tvis conName covariant ty
makeBiFunForType biFun tvis conName covariant (ForallT tvbs _ ty) =
makeBiFunForType biFun (removeForalled tvbs tvis) conName covariant ty
makeBiFunForType biFun tvis conName covariant ty =
let tyCon :: Type
tyArgs :: [Type]
tyCon:tyArgs = unapplyTy ty
numLastArgs :: Int
numLastArgs = min 2 $ length tyArgs
lhsArgs, rhsArgs :: [Type]
(lhsArgs, rhsArgs) = splitAt (length tyArgs - numLastArgs) tyArgs
tyVarNameBases :: [NameBase]
tyVarNameBases = map fst tvis
mentionsTyArgs :: Bool
mentionsTyArgs = any (`mentionsNameBase` tyVarNameBases) tyArgs
makeBiFunTuple :: Type -> Name -> Q Exp
makeBiFunTuple fieldTy fieldName =
makeBiFunForType biFun tvis conName covariant fieldTy `appE` varE fieldName
in case tyCon of
ArrowT
| not (allowFunTys (biFunToClass biFun)) -> noFunctionsError conName
| mentionsTyArgs, [argTy, resTy] <- tyArgs ->
do x <- newName "x"
b <- newName "b"
lamE [varP x, varP b] $
covBiFun covariant resTy `appE` (varE x `appE`
(covBiFun (not covariant) argTy `appE` varE b))
where
covBiFun :: Bool -> Type -> Q Exp
covBiFun = makeBiFunForType biFun tvis conName
TupleT n
| n > 0 && mentionsTyArgs -> do
args <- mapM newName $ catMaybes [ Just "x"
, guard (biFun == Bifoldr) >> Just "z"
]
xs <- newNameList "tup" n
let x = head args
z = last args
lamE (map varP args) $ caseE (varE x)
[ match (tupP $ map varP xs)
(normalB $ biFunCombine biFun
(tupleDataName n)
z
(zipWith makeBiFunTuple tyArgs xs)
)
[]
]
_ -> do
itf <- isTyFamily tyCon
if any (`mentionsNameBase` tyVarNameBases) lhsArgs || (itf && mentionsTyArgs)
then outOfPlaceTyVarError conName tyVarNameBases
else if any (`mentionsNameBase` tyVarNameBases) rhsArgs
then biFunApp biFun . appsE $
( varE (fromJust $ biFunArity biFun numLastArgs)
: map (makeBiFunForType biFun tvis conName covariant) rhsArgs
)
else biFunTriv biFun
withType :: Name
-> (Name -> Cxt -> [TyVarBndr] -> [Con] -> Maybe [Type] -> Q a)
-> Q a
withType name f = do
info <- reify name
case info of
TyConI dec ->
case dec of
DataD ctxt _ tvbs
#if MIN_VERSION_template_haskell(2,11,0)
_
#endif
cons _ -> f name ctxt tvbs cons Nothing
NewtypeD ctxt _ tvbs
#if MIN_VERSION_template_haskell(2,11,0)
_
#endif
con _ -> f name ctxt tvbs [con] Nothing
_ -> error $ ns ++ "Unsupported type: " ++ show dec
#if MIN_VERSION_template_haskell(2,7,0)
# if MIN_VERSION_template_haskell(2,11,0)
DataConI _ _ parentName -> do
# else
DataConI _ _ parentName _ -> do
# endif
parentInfo <- reify parentName
case parentInfo of
# if MIN_VERSION_template_haskell(2,11,0)
FamilyI (DataFamilyD _ tvbs _) decs ->
# else
FamilyI (FamilyD DataFam _ tvbs _) decs ->
# endif
let instDec = flip find decs $ \dec -> case dec of
DataInstD _ _ _
# if MIN_VERSION_template_haskell(2,11,0)
_
# endif
cons _ -> any ((name ==) . constructorName) cons
NewtypeInstD _ _ _
# if MIN_VERSION_template_haskell(2,11,0)
_
# endif
con _ -> name == constructorName con
_ -> error $ ns ++ "Must be a data or newtype instance."
in case instDec of
Just (DataInstD ctxt _ instTys
# if MIN_VERSION_template_haskell(2,11,0)
_
# endif
cons _)
-> f parentName ctxt tvbs cons $ Just instTys
Just (NewtypeInstD ctxt _ instTys
# if MIN_VERSION_template_haskell(2,11,0)
_
# endif
con _)
-> f parentName ctxt tvbs [con] $ Just instTys
_ -> error $ ns ++
"Could not find data or newtype instance constructor."
_ -> error $ ns ++ "Data constructor " ++ show name ++
" is not from a data family instance constructor."
# if MIN_VERSION_template_haskell(2,11,0)
FamilyI DataFamilyD{} _ ->
# else
FamilyI (FamilyD DataFam _ _ _) _ ->
# endif
error $ ns ++
"Cannot use a data family name. Use a data family instance constructor instead."
_ -> error $ ns ++ "The name must be of a plain data type constructor, "
++ "or a data family instance constructor."
#else
DataConI{} -> dataConIError
_ -> error $ ns ++ "The name must be of a plain type constructor."
#endif
where
ns :: String
ns = "Data.Bifunctor.TH.withType: "
buildTypeInstance :: BiClass
-> Name
-> Cxt
-> [TyVarBndr]
-> Maybe [Type]
-> (Cxt, Type, [NameBase])
buildTypeInstance biClass tyConName dataCxt tvbs Nothing
| remainingLength < 0 || not (wellKinded droppedKinds)
= derivingKindError biClass tyConName
| any (`predMentionsNameBase` droppedNbs) dataCxt
= datatypeContextError tyConName instanceType
| otherwise = (instanceCxt, instanceType, droppedNbs)
where
instanceCxt :: Cxt
instanceCxt = mapMaybe (applyConstraint biClass) remaining
instanceType :: Type
instanceType = AppT (ConT $ biClassName biClass)
. applyTyCon tyConName
$ map (VarT . tvbName) remaining
remainingLength :: Int
remainingLength = length tvbs - 2
remaining, dropped :: [TyVarBndr]
(remaining, dropped) = splitAt remainingLength tvbs
droppedKinds :: [Kind]
droppedKinds = map tvbKind dropped
droppedNbs :: [NameBase]
droppedNbs = map (NameBase . tvbName) dropped
buildTypeInstance biClass parentName dataCxt tvbs (Just instTysAndKinds)
| remainingLength < 0 || not (wellKinded droppedKinds)
= derivingKindError biClass parentName
| any (`predMentionsNameBase` droppedNbs) dataCxt
= datatypeContextError parentName instanceType
| canEtaReduce remaining dropped
= (instanceCxt, instanceType, droppedNbs)
| otherwise = etaReductionError instanceType
where
instanceCxt :: Cxt
instanceCxt = mapMaybe (applyConstraint biClass) lhsTvbs
instanceType :: Type
instanceType = AppT (ConT $ biClassName biClass)
. applyTyCon parentName
$ map unSigT remaining
remainingLength :: Int
remainingLength = length tvbs - 2
remaining, dropped :: [Type]
(remaining, dropped) = splitAt remainingLength rhsTypes
droppedKinds :: [Kind]
droppedKinds = map tvbKind . snd $ splitAt remainingLength tvbs
droppedNbs :: [NameBase]
droppedNbs = map varTToNameBase dropped
instTypes :: [Type]
instTypes =
#if __GLASGOW_HASKELL__ >= 710 || !(MIN_VERSION_template_haskell(2,8,0))
instTysAndKinds
#else
drop (Set.size . Set.unions $ map (distinctKindVars . tvbKind) tvbs)
instTysAndKinds
#endif
lhsTvbs :: [TyVarBndr]
lhsTvbs = map (uncurry replaceTyVarName)
. filter (isTyVar . snd)
. take remainingLength
$ zip tvbs rhsTypes
rhsTypes :: [Type]
rhsTypes =
#if __GLASGOW_HASKELL__ >= 708 && __GLASGOW_HASKELL__ < 710
instTypes ++ map tvbToType (drop (length instTypes) tvbs)
#else
instTypes
#endif
applyConstraint :: BiClass -> TyVarBndr -> Maybe Pred
applyConstraint _ PlainTV{} = Nothing
applyConstraint biClass (KindedTV name kind) = do
constraint <- biClassConstraint biClass $ numKindArrows kind
if canRealizeKindStarChain kind
then Just $ applyClass constraint name
else Nothing
derivingKindError :: BiClass -> Name -> a
derivingKindError biClass tyConName = error
. showString "Cannot derive well-kinded instance of form ‘"
. showString className
. showChar ' '
. showParen True
( showString (nameBase tyConName)
. showString " ..."
)
. showString "‘\n\tClass "
. showString className
. showString " expects an argument of kind * -> * -> *"
$ ""
where
className :: String
className = nameBase $ biClassName biClass
contravarianceError :: Name -> a
contravarianceError conName = error
. showString "Constructor ‘"
. showString (nameBase conName)
. showString "‘ must not use the last type variable(s) in a function argument"
$ ""
noFunctionsError :: Name -> a
noFunctionsError conName = error
. showString "Constructor ‘"
. showString (nameBase conName)
. showString "‘ must not contain function types"
$ ""
datatypeContextError :: Name -> Type -> a
datatypeContextError dataName instanceType = error
. showString "Can't make a derived instance of ‘"
. showString (pprint instanceType)
. showString "‘:\n\tData type ‘"
. showString (nameBase dataName)
. showString "‘ must not have a class context involving the last type argument(s)"
$ ""
existentialContextError :: Name -> a
existentialContextError conName = error
. showString "Constructor ‘"
. showString (nameBase conName)
. showString "‘ must be truly polymorphic in the last argument(s) of the data type"
$ ""
outOfPlaceTyVarError :: Name -> [NameBase] -> a
outOfPlaceTyVarError conName tyVarNames = error
. showString "Constructor ‘"
. showString (nameBase conName)
. showString "‘ must use the type variable(s) "
. shows tyVarNames
. showString " only in the last argument(s) of a data type"
$ ""
etaReductionError :: Type -> a
etaReductionError instanceType = error $
"Cannot eta-reduce to an instance of form \n\tinstance (...) => "
++ pprint instanceType
#if !(MIN_VERSION_template_haskell(2,7,0))
dataConIError :: a
dataConIError = error
. showString "Cannot use a data constructor."
. showString "\n\t(Note: if you are trying to derive for a data family instance,"
. showString "\n\tuse GHC >= 7.4 instead.)"
$ ""
#endif
data BiClass = Bifunctor | Bifoldable | Bitraversable
data BiFun = Bimap | Bifoldr | BifoldMap | Bitraverse
deriving Eq
biFunConstName :: BiFun -> Name
biFunConstName Bimap = bimapConstValName
biFunConstName Bifoldr = bifoldrConstValName
biFunConstName BifoldMap = bifoldMapConstValName
biFunConstName Bitraverse = bitraverseConstValName
biClassName :: BiClass -> Name
biClassName Bifunctor = bifunctorTypeName
biClassName Bifoldable = bifoldableTypeName
biClassName Bitraversable = bitraversableTypeName
biFunName :: BiFun -> Name
biFunName Bimap = bimapValName
biFunName Bifoldr = bifoldrValName
biFunName BifoldMap = bifoldMapValName
biFunName Bitraverse = bitraverseValName
biClassToFuns :: BiClass -> [BiFun]
biClassToFuns Bifunctor = [Bimap]
biClassToFuns Bifoldable = [Bifoldr, BifoldMap]
biClassToFuns Bitraversable = [Bitraverse]
biFunToClass :: BiFun -> BiClass
biFunToClass Bimap = Bifunctor
biFunToClass Bifoldr = Bifoldable
biFunToClass BifoldMap = Bifoldable
biFunToClass Bitraverse = Bitraversable
biClassConstraint :: BiClass -> Int -> Maybe Name
biClassConstraint Bifunctor 1 = Just functorTypeName
biClassConstraint Bifoldable 1 = Just foldableTypeName
biClassConstraint Bitraversable 1 = Just traversableTypeName
biClassConstraint biClass 2 = Just $ biClassName biClass
biClassConstraint _ _ = Nothing
biFunArity :: BiFun -> Int -> Maybe Name
biFunArity Bimap 1 = Just fmapValName
biFunArity Bifoldr 1 = Just foldrValName
biFunArity BifoldMap 1 = Just foldMapValName
biFunArity Bitraverse 1 = Just traverseValName
biFunArity biFun 2 = Just $ biFunName biFun
biFunArity _ _ = Nothing
allowFunTys :: BiClass -> Bool
allowFunTys Bifunctor = True
allowFunTys _ = False
allowExQuant :: BiClass -> Bool
allowExQuant Bifoldable = True
allowExQuant _ = False
biFunTriv :: BiFun -> Q Exp
biFunTriv Bimap = do
x <- newName "x"
lamE [varP x] $ varE x
biFunTriv Bifoldr = do
z <- newName "z"
lamE [wildP, varP z] $ varE z
biFunTriv BifoldMap = lamE [wildP] $ varE memptyValName
biFunTriv Bitraverse = varE pureValName
biFunApp :: BiFun -> Q Exp -> Q Exp
biFunApp Bifoldr e = do
x <- newName "x"
z <- newName "z"
lamE [varP x, varP z] $ appsE [e, varE z, varE x]
biFunApp _ e = e
biFunCombine :: BiFun -> Name -> Name -> [Q Exp] -> Q Exp
biFunCombine Bimap = bimapCombine
biFunCombine Bifoldr = bifoldrCombine
biFunCombine BifoldMap = bifoldMapCombine
biFunCombine Bitraverse = bitraverseCombine
bimapCombine :: Name -> Name -> [Q Exp] -> Q Exp
bimapCombine conName _ = foldl' appE (conE conName)
bifoldrCombine :: Name -> Name -> [Q Exp] -> Q Exp
bifoldrCombine _ zName = foldr appE (varE zName)
bifoldMapCombine :: Name -> Name -> [Q Exp] -> Q Exp
bifoldMapCombine _ _ [] = varE memptyValName
bifoldMapCombine _ _ es = foldr1 (appE . appE (varE mappendValName)) es
bitraverseCombine :: Name -> Name -> [Q Exp] -> Q Exp
bitraverseCombine conName _ [] = varE pureValName `appE` conE conName
bitraverseCombine conName _ (e:es) =
foldl' (flip infixApp $ varE apValName)
(appsE [varE fmapValName, conE conName, e]) es