{-# LANGUAGE
DeriveFunctor
, DeriveFoldable
, TemplateHaskell
, TypeOperators
, CPP #-}
module Data.Label.Derive
(
mkLabel
, mkLabels
, mkLabelsNamed
, getLabel
, fclabels
, mkLabelsWith
, getLabelWith
, defaultNaming
)
where
import Control.Applicative
import Control.Arrow
import Control.Category
import Control.Monad
import Data.Char (toLower, toUpper)
#if MIN_VERSION_base(4,8,0)
import Data.Foldable (toList)
#else
import Data.Foldable (Foldable, toList)
#endif
import Data.Label.Point
import Data.List (groupBy, sortBy, delete, nub)
import Data.Maybe (fromMaybe)
import Data.Ord
#if MIN_VERSION_template_haskell(2,10,0)
import Language.Haskell.TH hiding (classP)
#else
import Language.Haskell.TH
#endif
import Prelude hiding ((.), id)
import qualified Data.Label.Mono as Mono
import qualified Data.Label.Poly as Poly
mkLabels :: [Name] -> Q [Dec]
mkLabels = liftM concat . mapM (mkLabelsWith defaultNaming True False False True)
mkLabel :: Name -> Q [Dec]
mkLabel = mkLabels . return
mkLabelsNamed :: (String -> String) -> [Name] -> Q [Dec]
mkLabelsNamed mk = liftM concat . mapM (mkLabelsWith mk True False False True)
getLabel :: Name -> Q Exp
getLabel = getLabelWith True False False
getLabelWith
:: Bool
-> Bool
-> Bool
-> Name
-> Q Exp
getLabelWith sigs concrete failing name =
do dec <- reifyDec name
labels <- generateLabels id concrete failing dec
let bodies = map (\(LabelExpr _ _ _ b) -> b) labels
types = map (\(LabelExpr _ _ t _) -> t) labels
context = head $ map (\(LabelExpr _ c _ _) -> c) labels
vars = head $ map (\(LabelExpr v _ _ _) -> v) labels
if sigs
then tupE bodies `sigE`
forallT vars context (foldl appT (tupleT (length bodies)) types)
else tupE bodies
mkLabelsWith
:: (String -> String)
-> Bool
-> Bool
-> Bool
-> Bool
-> Name
-> Q [Dec]
mkLabelsWith mk sigs concrete failing inl name =
do dec <- reifyDec name
mkLabelsWithForDec mk sigs concrete failing inl dec
defaultNaming :: String -> String
defaultNaming field =
case field of
'_' : c : rest -> toLower c : rest
f : rest -> 'l' : toUpper f : rest
n -> fclError ("Cannot derive label for record selector with name: " ++ n)
fclabels :: Q [Dec] -> Q [Dec]
fclabels decls =
do ds <- decls
ls <- forM (ds >>= labels) (mkLabelsWithForDec id True False False False)
return (concat ((delabelize <$> ds) : ls))
where
labels :: Dec -> [Dec]
labels dec =
case dec of
DataD {} -> [dec]
NewtypeD {} -> [dec]
_ -> []
delabelize :: Dec -> Dec
delabelize dec =
case dec of
#if MIN_VERSION_template_haskell(2,11,0)
DataD ctx nm vars mk cs ns -> DataD ctx nm vars mk (con <$> cs) ns
NewtypeD ctx nm vars mk c ns -> NewtypeD ctx nm vars mk (con c) ns
#else
DataD ctx nm vars cs ns -> DataD ctx nm vars (con <$> cs) ns
NewtypeD ctx nm vars c ns -> NewtypeD ctx nm vars (con c) ns
#endif
rest -> rest
where con (RecC n vst) = NormalC n (map (\(_, s, t) -> (s, t)) vst)
#if MIN_VERSION_template_haskell(2,11,0)
con (RecGadtC ns vst ty) = GadtC ns (map (\(_, s, t) -> (s, t)) vst) ty
#endif
con c = c
data Label
= LabelDecl
Name
DecQ
[TyVarBndr]
CxtQ
TypeQ
ExpQ
| LabelExpr
[TyVarBndr]
CxtQ
TypeQ
ExpQ
data Field c = Field
(Maybe Name)
Bool
Type
c
deriving (Eq, Functor, Foldable)
type Subst = [(Type, Type)]
data Context = Context
Int
Name
Con
deriving (Eq, Show)
data Typing = Typing
Bool
TypeQ
TypeQ
[TyVarBndr]
mkLabelsWithForDec :: (String -> String) -> Bool -> Bool -> Bool -> Bool -> Dec -> Q [Dec]
mkLabelsWithForDec mk sigs concrete failing inl dec =
do labels <- generateLabels mk concrete failing dec
decls <- forM labels $ \l ->
case l of
LabelExpr {} -> return []
LabelDecl n i v c t b ->
do bdy <- pure <$> funD n [clause [] (normalB b) []]
prg <- if inl then pure <$> i else return []
typ <- if sigs
then pure <$> sigD n (forallT v c t)
else return []
return (concat [prg, typ, bdy])
return (concat decls)
generateLabels :: (String -> String) -> Bool -> Bool -> Dec -> Q [Label]
generateLabels mk concrete failing dec =
do
let (name, cons, vars) =
case dec of
#if MIN_VERSION_template_haskell(2,11,0)
DataD _ n vs _ cs _ -> (n, cs, vs)
NewtypeD _ n vs _ c _ -> (n, [c], vs)
#else
DataD _ n vs cs _ -> (n, cs, vs)
NewtypeD _ n vs c _ -> (n, [c], vs)
#endif
_ -> fclError "Can only derive labels for datatypes and newtypes."
fields = groupFields mk vars cons
forM fields $ generateLabel failing concrete name vars cons
groupFields :: (String -> String) -> [TyVarBndr] -> [Con] -> [Field ([Context], Subst)]
groupFields mk vs
= map (rename mk)
. concatMap (\fs -> let vals = concat (toList <$> fs)
cons = fst <$> vals
subst = concat (snd <$> vals)
in nub (fmap (const (cons, subst)) <$> fs)
)
. groupBy eq
. sortBy (comparing name)
. concatMap (constructorFields vs)
where name (Field n _ _ _) = n
eq f g = False `fromMaybe` ((==) <$> name f <*> name g)
rename f (Field n a b c) =
Field (mkName . f . nameBase <$> n) a b c
constructorFields :: [TyVarBndr] -> Con -> [Field (Context, Subst)]
constructorFields vs con =
case con of
NormalC c fs -> one <$> zip [0..] fs
where one (i, f@(_, ty)) = Field Nothing mono ty (Context i c con, [])
where fsTys = map (typeVariables . snd) (delete f fs)
mono = any (\x -> any (elem x) fsTys) (typeVariables ty)
RecC c fs -> one <$> zip [0..] fs
where one (i, f@(n, _, ty)) = Field (Just n) mono ty (Context i c con, [])
where fsTys = map (typeVariables . trd) (delete f fs)
mono = any (\x -> any (elem x) fsTys) (typeVariables ty)
InfixC a c b -> one <$> [(0, a), (1, b)]
where one (i, (_, ty)) = Field Nothing mono ty (Context i c con, [])
where fsTys = map (typeVariables . snd) [a, b]
mono = any (\x -> any (elem x) fsTys) (typeVariables ty)
ForallC x y v -> setEqs <$> constructorFields vs v
#if MIN_VERSION_template_haskell(2,10,0)
where eqs = [ (a, b) | AppT (AppT EqualityT a) b <- y ]
#else
where eqs = [ (a, b) | EqualP a b <- y ]
#endif
setEqs (Field a b c d) = Field a b c (first upd . second (eqs ++) $ d)
upd (Context a b c) = Context a b (ForallC x y c)
#if MIN_VERSION_template_haskell(2,11,0)
GadtC cs fs resTy -> concatMap (\c -> one c <$> zip [0..] fs) cs
where one c (i, f@(_, ty)) = Field Nothing mono ty (Context i c con, mkSubst vs resTy)
where fsTys = map (typeVariables . snd) (delete f fs)
mono = any (\x -> any (elem x) fsTys) (typeVariables ty)
RecGadtC cs fs resTy -> concatMap (\c -> one c <$> zip [0..] fs) cs
where one c (i, f@(n, _, ty)) = Field (Just n) mono ty (Context i c con, mkSubst vs resTy)
where fsTys = map (typeVariables . trd) (delete f fs)
mono = any (\x -> any (elem x) fsTys) (typeVariables ty)
mkSubst :: [TyVarBndr] -> Type -> Subst
mkSubst vars t = go (reverse vars) t
where
go [] _ = []
go (v:vs) (AppT t1 t2) = (typeFromBinder v, t2) : go vs t1
go _ _ = fclError "Non-AppT with type variables in mkSubst. Please report this as a bug for fclabels."
#endif
prune :: [Context] -> [Con] -> [Con]
prune contexts allCons =
case contexts of
(Context _ _ con) : _
-> filter (unifiableCon con) allCons
[] -> []
unifiableCon :: Con -> Con -> Bool
unifiableCon a b = and (zipWith unifiable (indices a) (indices b))
where indices con =
case con of
NormalC {} -> []
RecC {} -> []
InfixC {} -> []
#if MIN_VERSION_template_haskell(2,11,0)
ForallC _ _ ty -> indices ty
#elif MIN_VERSION_template_haskell(2,10,0)
ForallC _ x _ -> [ c | AppT (AppT EqualityT _) c <- x ]
#else
ForallC _ x _ -> [ c | EqualP _ c <- x ]
#endif
#if MIN_VERSION_template_haskell(2,11,0)
GadtC _ _ ty -> conIndices ty
RecGadtC _ _ ty -> conIndices ty
where
conIndices (AppT (ConT _) ty) = [ty]
conIndices (AppT rest ty) = conIndices rest ++ [ty]
conIndices _ = fclError "Non-AppT in conIndices. Please report this as a bug for fclabels."
#endif
unifiable :: Type -> Type -> Bool
unifiable x y =
case (x, y) of
( VarT _ , _ ) -> True
( _ , VarT _ ) -> True
( AppT a b , AppT c d ) -> unifiable a c && unifiable b d
( SigT t k , SigT s j ) -> unifiable t s && k == j
( ForallT _ _ t , ForallT _ _ s ) -> unifiable t s
( a , b ) -> a == b
generateLabel
:: Bool
-> Bool
-> Name
-> [TyVarBndr]
-> [Con]
-> Field ([Context], Subst)
-> Q Label
generateLabel failing concrete datatype dtVars allCons
field@(Field name forcedMono fieldtype (contexts, subst)) =
do let total = length contexts == length (prune contexts allCons)
(Typing mono tyI tyO _)
<- computeTypes forcedMono fieldtype datatype dtVars subst
let cat = varT (mkName "cat")
failE = if failing
then [| failArrow |]
else [| zeroArrow |]
getT = [| arr $(getter failing total field) |]
putT = [| arr $(setter failing total field) |]
getP = [| $(failE) ||| id <<< $getT |]
putP = [| $(failE) ||| id <<< $putT |]
failP = if failing
then classP ''ArrowFail [ [t| String |], cat]
else classP ''ArrowZero [cat]
ctx = if total
then cxt [ classP ''ArrowApply [cat] ]
else cxt [ classP ''ArrowChoice [cat]
, classP ''ArrowApply [cat]
, failP
]
body = if total
then [| Poly.point $ Point $getT (modifier $getT $putT) |]
else [| Poly.point $ Point $getP (modifier $getP $putP) |]
cont = if concrete
then cxt []
else ctx
partial = if failing
then [t| Failing String |]
else [t| Partial |]
concTy = if total
then if mono
then [t| Mono.Lens Total $tyI $tyO |]
else [t| Poly.Lens Total $tyI $tyO |]
else if mono
then [t| Mono.Lens $partial $tyI $tyO |]
else [t| Poly.Lens $partial $tyI $tyO |]
ty = if concrete
then concTy
else if mono
then [t| Mono.Lens $cat $tyI $tyO |]
else [t| Poly.Lens $cat $tyI $tyO |]
tvs <- nub . binderFromType <$> ty
return $
case name of
Nothing -> LabelExpr tvs cont ty body
Just n ->
#if MIN_VERSION_template_haskell(2,8,0)
let inline = InlineP n Inline FunLike (FromPhase 0)
#else
let inline = InlineP n (InlineSpec True True (Just (True, 0)))
#endif
in LabelDecl n (return (PragmaD inline)) tvs cont ty body
modifier :: ArrowApply cat => cat f o -> cat (i, f) g -> cat (cat o i, f) g
modifier g m = m . first app . arr (\(n, (f, o)) -> ((n, o), f)) . second (id &&& g)
{-# INLINE modifier #-}
getter :: Bool -> Bool -> Field ([Context], Subst) -> Q Exp
getter failing total (Field mn _ _ (cons, _)) =
do let pt = mkName "f"
nm = maybe (tupE []) (litE . StringL . nameBase) (guard failing >> mn)
wild = if total then [] else [match wildP (normalB [| Left $(nm) |]) []]
rght = if total then id else appE [| Right |]
mkCase (Context i _ c) = map (\(pat, var) -> match pat (normalB (rght var)) []) (case1 i c)
lamE [varP pt]
(caseE (varE pt) (concatMap mkCase cons ++ wild))
where
case1 :: Int -> Con -> [(Q Pat, Q Exp)]
case1 i con =
case con of
NormalC c fs -> [one fs c]
RecC c fs -> [one fs c]
InfixC _ c _ -> [(infixP (pats !! 0) c (pats !! 1), var)]
ForallC _ _ c -> case1 i c
#if MIN_VERSION_template_haskell(2,11,0)
GadtC cs fs _ -> map (one fs) cs
RecGadtC cs fs _ -> map (one fs) cs
#endif
where fresh = mkName <$> delete "f" freshNames
pats1 = varP <$> fresh
pats = replicate i wildP ++ [pats1 !! i] ++ repeat wildP
var = varE (fresh !! i)
one fs c = let s = take (length fs) in (conP c (s pats), var)
setter :: Bool -> Bool -> Field ([Context], Subst) -> Q Exp
setter failing total (Field mn _ _ (cons, _)) =
do let pt = mkName "f"
md = mkName "v"
nm = maybe (tupE []) (litE . StringL . nameBase) (guard failing >> mn)
wild = if total then [] else [match wildP (normalB [| Left $(nm) |]) []]
rght = if total then id else appE [| Right |]
mkCase (Context i _ c) = map (\(pat, var) -> match pat (normalB (rght var)) []) (case1 i c)
lamE [tupP [varP md, varP pt]]
(caseE (varE pt) (concatMap mkCase cons ++ wild))
where
case1 i con =
case con of
NormalC c fs -> [one fs c]
RecC c fs -> [one fs c]
InfixC _ c _ -> [( infixP (pats !! 0) c (pats !! 1)
, infixE (Just (vars !! 0)) (conE c) (Just (vars !! 1))
)
]
ForallC _ _ c -> case1 i c
#if MIN_VERSION_template_haskell(2,11,0)
GadtC cs fs _ -> map (one fs) cs
RecGadtC cs fs _ -> map (one fs) cs
#endif
where fresh = mkName <$> delete "f" (delete "v" freshNames)
pats1 = varP <$> fresh
pats = take i pats1 ++ [wildP] ++ drop (i + 1) pats1
vars1 = varE <$> fresh
v = varE (mkName "v")
vars = take i vars1 ++ [v] ++ drop (i + 1) vars1
apps f as = foldl appE f as
one fs c = let s = take (length fs) in (conP c (s pats), apps (conE c) (s vars))
freshNames :: [String]
freshNames = map pure ['a'..'z'] ++ map (('a':) . show) [0 :: Integer ..]
computeTypes :: Bool -> Type -> Name -> [TyVarBndr] -> Subst -> Q Typing
computeTypes forcedMono fieldtype datatype dtVars_ subst =
do let fieldVars = typeVariables fieldtype
tyO = return fieldtype
dtTypes = substitute subst . typeFromBinder <$> dtVars_
dtBinders = concatMap binderFromType dtTypes
varNames = nameFromBinder <$> dtBinders
usedVars = filter (`elem` fieldVars) varNames
tyI = return $ foldr (flip AppT) (ConT datatype) (reverse dtTypes)
pretties = mapTyVarBndr pretty <$> dtBinders
mono = forcedMono || isMonomorphic fieldtype dtBinders
if mono
then return $ Typing
mono
(prettyType <$> tyI)
(prettyType <$> tyO)
(nub pretties)
else
do let names = return <$> ['a'..'z']
used = show . pretty <$> varNames
free = filter (not . (`elem` used)) names
subs <- forM (zip usedVars free) (\(a, b) -> (,) a <$> newName b)
let rename = mapTypeVariables (\a -> a `fromMaybe` lookup a subs)
return $ Typing
mono
(prettyType <$> [t| $tyI -> $(rename <$> tyI) |])
(prettyType <$> [t| $tyO -> $(rename <$> tyO) |])
(nub (pretties ++ map (mapTyVarBndr pretty) (PlainTV . snd <$> subs)))
isMonomorphic :: Type -> [TyVarBndr] -> Bool
isMonomorphic field vars =
let fieldVars = typeVariables field
varNames = nameFromBinder <$> vars
usedVars = filter (`elem` fieldVars) varNames
in null usedVars
typeVariables :: Type -> [Name]
typeVariables = map nameFromBinder . binderFromType
typeFromBinder :: TyVarBndr -> Type
typeFromBinder (PlainTV tv ) = VarT tv
#if MIN_VERSION_template_haskell(2,8,0)
typeFromBinder (KindedTV tv StarT) = VarT tv
#else
typeFromBinder (KindedTV tv StarK) = VarT tv
#endif
typeFromBinder (KindedTV tv kind ) = SigT (VarT tv) kind
binderFromType :: Type -> [TyVarBndr]
binderFromType = go
where
go ty =
case ty of
ForallT ts _ _ -> ts
AppT a b -> go a ++ go b
SigT t _ -> go t
VarT n -> [PlainTV n]
_ -> []
mapTypeVariables :: (Name -> Name) -> Type -> Type
mapTypeVariables f = go
where
go ty =
case ty of
ForallT ts a b -> ForallT (mapTyVarBndr f <$> ts)
(mapPred f <$> a) (go b)
AppT a b -> AppT (go a) (go b)
SigT t a -> SigT (go t) a
VarT n -> VarT (f n)
t -> t
mapType :: (Type -> Type) -> Type -> Type
mapType f = go
where
go ty =
case ty of
ForallT v c t -> f (ForallT v c (go t))
AppT a b -> f (AppT (go a) (go b))
SigT t k -> f (SigT (go t) k)
_ -> f ty
substitute :: Subst -> Type -> Type
substitute env = mapType sub
where sub v = case lookup v env of
Nothing -> v
Just w -> w
nameFromBinder :: TyVarBndr -> Name
nameFromBinder (PlainTV n ) = n
nameFromBinder (KindedTV n _) = n
mapPred :: (Name -> Name) -> Pred -> Pred
#if MIN_VERSION_template_haskell(2,10,0)
mapPred = mapTypeVariables
#else
mapPred f (ClassP n ts) = ClassP (f n) (mapTypeVariables f <$> ts)
mapPred f (EqualP t x ) = EqualP (mapTypeVariables f t) (mapTypeVariables f x)
#endif
mapTyVarBndr :: (Name -> Name) -> TyVarBndr -> TyVarBndr
mapTyVarBndr f (PlainTV n ) = PlainTV (f n)
mapTyVarBndr f (KindedTV n a) = KindedTV (f n) a
pretty :: Name -> Name
pretty tv = mkName (takeWhile (/= '_') (show tv))
prettyType :: Type -> Type
prettyType = mapTypeVariables pretty
reifyDec :: Name -> Q Dec
reifyDec name =
do info <- reify name
case info of
TyConI dec -> return dec
_ -> fclError "Info must be type declaration type."
fclError :: String -> a
fclError err = error ("Data.Label.Derive: " ++ err)
#if MIN_VERSION_template_haskell(2,10,0)
classP :: Name -> [Q Type] -> Q Pred
classP cla tys
= do tysl <- sequence tys
return (foldl AppT (ConT cla) tysl)
#endif
trd :: (a, b, c) -> c
trd (_, _, x) = x