{-# LANGUAGE CPP #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FunctionalDependencies #-}
#ifdef TRUSTWORTHY
{-# LANGUAGE Trustworthy #-}
#endif
#ifndef MIN_VERSION_template_haskell
#define MIN_VERSION_template_haskell(x,y,z) (defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 706)
#endif
module Control.Lens.TH
(
makeLenses, makeLensesFor
, makeClassy, makeClassyFor, makeClassy_
, makePrisms
, makeWrapped
, makeFields
, declareLenses, declareLensesFor
, declareClassy, declareClassyFor
, declarePrisms
, declareWrapped
, declareFields
, makeLensesWith
, makeFieldsWith
, declareLensesWith
, declareFieldsWith
, defaultRules
, defaultFieldRules
, camelCaseFields
, underscoreFields
, LensRules(LensRules)
, FieldRules(FieldRules)
, lensRules
, classyRules
, classyRules_
, lensIso
, lensField
, lensClass
, lensFlags
, LensFlag(..)
, simpleLenses
, partialLenses
, buildTraversals
, handleSingletons
, singletonIso
, singletonRequired
, createClass
, createInstance
, classRequired
, singletonAndField
, generateSignatures
) where
import Control.Applicative
import Control.Monad ((<=<), when, replicateM)
#if !(MIN_VERSION_template_haskell(2,7,0))
import Control.Monad (ap)
#endif
import qualified Control.Monad.Trans as Trans
import Control.Monad.Trans.Writer
import Control.Lens.At
import Control.Lens.Fold
import Control.Lens.Getter
import Control.Lens.Iso
import Control.Lens.Lens
import Control.Lens.Prism
import Control.Lens.Review
import Control.Lens.Setter
import Control.Lens.Tuple
import Control.Lens.Traversal
import Control.Lens.Wrapped
import Control.Lens.Internal.TH
import Data.Char (toLower, toUpper, isUpper)
import Data.Either (lefts)
import Data.Foldable hiding (concat, any)
import Data.Function (on)
import Data.List as List
import Data.Map as Map hiding (toList,map,filter)
import Data.Maybe as Maybe (isNothing,isJust,catMaybes,fromJust,mapMaybe)
import Data.Monoid
import Data.Ord (comparing)
import Data.Set as Set hiding (toList,map,filter)
import Data.Set.Lens
import Data.Traversable hiding (mapM)
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import Language.Haskell.TH.Lens
#ifdef HLINT
{-# ANN module "HLint: ignore Eta reduce" #-}
{-# ANN module "HLint: ignore Use fewer imports" #-}
{-# ANN module "HLint: ignore Use foldl" #-}
#endif
data LensFlag
= SimpleLenses
| PartialLenses
| BuildTraversals
| SingletonAndField
| SingletonIso
| HandleSingletons
| SingletonRequired
| CreateClass
| CreateInstance
| ClassRequired
| GenerateSignatures
deriving (Eq,Ord,Show,Read)
simpleLenses :: Lens' LensRules Bool
simpleLenses = lensFlags.contains SimpleLenses
partialLenses :: Lens' LensRules Bool
partialLenses = lensFlags.contains PartialLenses
buildTraversals :: Lens' LensRules Bool
buildTraversals = lensFlags.contains BuildTraversals
handleSingletons :: Lens' LensRules Bool
handleSingletons = lensFlags.contains HandleSingletons
singletonAndField :: Lens' LensRules Bool
singletonAndField = lensFlags.contains SingletonAndField
singletonIso :: Lens' LensRules Bool
singletonIso = lensFlags.contains SingletonIso
singletonRequired :: Lens' LensRules Bool
singletonRequired = lensFlags.contains SingletonRequired
createClass :: Lens' LensRules Bool
createClass = lensFlags.contains CreateClass
createInstance :: Lens' LensRules Bool
createInstance = lensFlags.contains CreateInstance
classRequired :: Lens' LensRules Bool
classRequired = lensFlags.contains ClassRequired
generateSignatures :: Lens' LensRules Bool
generateSignatures = lensFlags.contains GenerateSignatures
data LensRules = LensRules
{ _lensIso :: String -> Maybe String
, _lensField :: String -> Maybe String
, _lensClass :: String -> Maybe (String, String)
, _lensFlags :: Set LensFlag
}
lensIso :: Lens' LensRules (String -> Maybe String)
lensIso f (LensRules i n c o) = f i <&> \i' -> LensRules i' n c o
lensField :: Lens' LensRules (String -> Maybe String)
lensField f (LensRules i n c o) = f n <&> \n' -> LensRules i n' c o
lensClass :: Lens' LensRules (String -> Maybe (String, String))
lensClass f (LensRules i n c o) = f c <&> \c' -> LensRules i n c' o
lensFlags :: Lens' LensRules (Set LensFlag)
lensFlags f (LensRules i n c o) = f o <&> LensRules i n c
defaultRules :: LensRules
defaultRules = LensRules mLowerName fld (const Nothing) $
Set.fromList [SingletonIso, SingletonAndField, CreateClass, CreateInstance, BuildTraversals, GenerateSignatures]
where
fld ('_':cs) = mLowerName cs
fld _ = Nothing
mLowerName :: String -> Maybe String
mLowerName (c:cs) = Just (toLower c:cs)
mLowerName _ = Nothing
lensRules :: LensRules
lensRules = defaultRules
& lensIso .~ const Nothing
& lensClass .~ const Nothing
& handleSingletons .~ True
& partialLenses .~ False
& buildTraversals .~ True
lensRulesFor :: [(String, String)] -> LensRules
lensRulesFor fields = lensRules & lensField .~ (`Prelude.lookup` fields)
classyRules :: LensRules
classyRules = defaultRules
& lensIso .~ const Nothing
& handleSingletons .~ False
& lensClass .~ classy
& classRequired .~ True
& partialLenses .~ False
& buildTraversals .~ True
where
classy :: String -> Maybe (String, String)
classy n@(a:as) = Just ("Has" ++ n, toLower a:as)
classy _ = Nothing
classyRulesFor
:: (String -> Maybe (String, String)) -> [(String, String)] -> LensRules
classyRulesFor classFun fields = classyRules
& lensClass .~ classFun
& lensField .~ (`Prelude.lookup` fields)
underscorePrefixRules :: LensRules
underscorePrefixRules = LensRules mLowerName fld (const Nothing) $
Set.fromList [SingletonIso, SingletonAndField, CreateClass,
CreateInstance, BuildTraversals, GenerateSignatures]
where
fld cs = Just ('_':cs)
classyRules_ :: LensRules
classyRules_ = underscorePrefixRules
& lensIso .~ const Nothing
& handleSingletons .~ False
& lensClass .~ classy
& classRequired .~ True
& partialLenses .~ False
& buildTraversals .~ True
where
classy :: String -> Maybe (String, String)
classy n@(a:as) = Just ("Has" ++ n, toLower a:as)
classy _ = Nothing
makeLenses :: Name -> Q [Dec]
makeLenses = makeLensesWith lensRules
makeClassy :: Name -> Q [Dec]
makeClassy = makeLensesWith classyRules
makeClassy_ :: Name -> Q [Dec]
makeClassy_ = makeLensesWith classyRules_
makeLensesFor :: [(String, String)] -> Name -> Q [Dec]
makeLensesFor fields = makeLensesWith $ lensRulesFor fields
makeClassyFor :: String -> String -> [(String, String)] -> Name -> Q [Dec]
makeClassyFor clsName funName fields = makeLensesWith $
classyRulesFor (const $ Just (clsName, funName)) fields
makeLensesWith :: LensRules -> Name -> Q [Dec]
makeLensesWith cfg nm = do
inf <- reify nm
case inf of
TyConI decl -> makeLensesForDec cfg decl
_ -> fail "makeLensesWith: Expected the name of a data type or newtype"
makePrisms :: Name -> Q [Dec]
makePrisms nm = do
inf <- reify nm
case inf of
TyConI decl -> makePrismsForDec decl
_ -> fail "makePrisms: Expected the name of a data type or newtype"
declareLenses :: Q [Dec] -> Q [Dec]
declareLenses = declareLensesWith (lensRules & lensField .~ Just)
declareLensesFor :: [(String, String)] -> Q [Dec] -> Q [Dec]
declareLensesFor fields = declareLensesWith $
lensRulesFor fields & lensField .~ Just
declareClassy :: Q [Dec] -> Q [Dec]
declareClassy = declareLensesWith (classyRules & lensField .~ Just)
declareClassyFor :: [(String, (String, String))] -> [(String, String)] -> Q [Dec] -> Q [Dec]
declareClassyFor classes fields = declareLensesWith $
classyRulesFor (`Prelude.lookup`classes) fields & lensField .~ Just
declarePrisms :: Q [Dec] -> Q [Dec]
declarePrisms = declareWith $ \dec -> do
emit =<< Trans.lift (makePrismsForDec dec)
return dec
declareWrapped :: Q [Dec] -> Q [Dec]
declareWrapped = declareWith $ \dec -> do
maybeDecs <- Trans.lift (makeWrappedForDec dec)
forM_ maybeDecs emit
return dec
declareFields :: Q [Dec] -> Q [Dec]
declareFields = declareFieldsWith defaultFieldRules
declareLensesWith :: LensRules -> Q [Dec] -> Q [Dec]
declareLensesWith rules = declareWith $ \dec -> do
emit =<< Trans.lift (makeLensesForDec rules dec)
return $ stripFields dec
declareFieldsWith :: FieldRules -> Q [Dec] -> Q [Dec]
declareFieldsWith rules = declareWith $ \dec -> do
emit =<< Trans.lift (makeFieldsForDec rules dec)
return $ stripFields dec
deNewtype :: Dec -> Dec
deNewtype (NewtypeD ctx tyName args c d) = DataD ctx tyName args [c] d
deNewtype (NewtypeInstD ctx tyName args c d) = DataInstD ctx tyName args [c] d
deNewtype d = d
makePrismsForDec :: Dec -> Q [Dec]
makePrismsForDec decl = case makeDataDecl decl of
Just dataDecl -> makePrismsForCons dataDecl
_ -> fail "makePrisms: Unsupported data type"
makePrismsForCons :: DataDecl -> Q [Dec]
makePrismsForCons dataDecl@(DataDecl _ _ _ _ [_]) = case constructors dataDecl of
[NormalC dataConName xs] ->
makeIsoLenses rules dataDecl dataConName Nothing $ map (view _2) xs
[RecC dataConName xs] ->
makeIsoLenses rules dataDecl dataConName Nothing $ map (view _3) xs
_ ->
fail "makePrismsForCons: A single-constructor data type is required"
where
rules = defaultRules
& handleSingletons .~ True
& singletonRequired .~ True
& singletonAndField .~ True
& lensIso .~ (Just . ('_':))
makePrismsForCons dataDecl =
concat <$> mapM (makePrismOrReviewForCon dataDecl canModifyTypeVar ) (constructors dataDecl)
where
conTypeVars = map (Set.fromList . toListOf typeVars) (constructors dataDecl)
canModifyTypeVar = (`Set.member` typeVarsOnlyInOneCon) . view name
typeVarsOnlyInOneCon = Set.fromList . concat . filter (\xs -> length xs == 1) . List.group . List.sort $ conTypeVars >>= toList
onlyBuildReview :: Con -> Bool
onlyBuildReview ForallC{} = True
onlyBuildReview _ = False
makePrismOrReviewForCon :: DataDecl -> (TyVarBndr -> Bool) -> Con -> Q [Dec]
makePrismOrReviewForCon dataDecl canModifyTypeVar con
| onlyBuildReview con = makeReviewForCon dataDecl con
| otherwise = makePrismForCon dataDecl canModifyTypeVar con
makeReviewForCon :: DataDecl -> Con -> Q [Dec]
makeReviewForCon dataDecl con = do
let functionName = mkName ('_': nameBase dataConName)
(dataConName, fieldTypes) = ctrNameAndFieldTypes con
sName <- newName "s"
aName <- newName "a"
fieldNames <- replicateM (length fieldTypes) (newName "x")
let s = varT sName
t = return (fullType dataDecl (map (VarT . view name) (dataParameters dataDecl)))
a = varT aName
b = toTupleT (map return fieldTypes)
(conTyVars, conCxt) = case con of ForallC x y _ -> (x,y)
_ -> ([],[])
functionType = forallT (map PlainTV [sName, aName] ++ conTyVars ++ dataParameters dataDecl)
(return conCxt)
(conT ''Review `appsT` [s,t,a,b])
let pat = toTupleP (map varP fieldNames)
lam = lam1E pat (conE dataConName `appsE1` map varE fieldNames)
body = varE 'unto `appE` lam
Prelude.sequence
[ sigD functionName functionType
, funD functionName [clause [] (normalB body) []]
]
makePrismForCon :: DataDecl -> (TyVarBndr -> Bool) -> Con -> Q [Dec]
makePrismForCon dataDecl canModifyTypeVar con = do
remitterName <- newName "remitter"
reviewerName <- newName "reviewer"
xName <- newName "x"
let resName = mkName $ '_': nameBase dataConName
varNames <- for [0..length fieldTypes -1] $ \i -> newName ('x' : show i)
let args = dataParameters dataDecl
altArgsList <- forM (view name <$> filter isAltArg args) $ \arg ->
(,) arg <$> newName (nameBase arg)
let altArgs = Map.fromList altArgsList
hitClause =
clause [conP dataConName (fmap varP varNames)]
(normalB $ appE (conE 'Right) $ toTupleE $ varE <$> varNames) []
otherCons = filter (/= con) (constructors dataDecl)
missClauses
| List.null otherCons = []
| Map.null altArgs = [clause [varP xName] (normalB (appE (conE 'Left) (varE xName))) []]
| otherwise = reviewerIdClause <$> otherCons
Prelude.sequence [
sigD resName . forallT
(args ++ (PlainTV <$> Map.elems altArgs))
(return $ List.nub (dataContext dataDecl ++ substTypeVars altArgs (dataContext dataDecl))) $
if List.null altArgsList then
conT ''Prism' `appsT`
[ return $ fullType dataDecl $ VarT . view name <$> args
, toTupleT $ pure <$> fieldTypes
]
else
conT ''Prism `appsT`
[ return $ fullType dataDecl $ VarT . view name <$> args
, return $ fullType dataDecl $ VarT . view name <$> substTypeVars altArgs args
, toTupleT $ pure <$> fieldTypes
, toTupleT $ pure <$> substTypeVars altArgs fieldTypes
]
, funD resName
[ clause []
(normalB (appsE [varE 'prism, varE remitterName, varE reviewerName]))
[ funD remitterName
[ clause [toTupleP (varP <$> varNames)] (normalB (conE dataConName `appsE1` fmap varE varNames)) [] ]
, funD reviewerName $ hitClause : missClauses
]
]
]
where
(dataConName, fieldTypes) = ctrNameAndFieldTypes con
conArgs = setOf typeVars fieldTypes
isAltArg arg = canModifyTypeVar arg && conArgs^.contains(arg^.name)
ctrNameAndFieldTypes :: Con -> (Name, [Type])
ctrNameAndFieldTypes (NormalC n ts) = (n, snd <$> ts)
ctrNameAndFieldTypes (RecC n ts) = (n, view _3 <$> ts)
ctrNameAndFieldTypes (InfixC l n r) = (n, [snd l, snd r])
ctrNameAndFieldTypes (ForallC _ _ c) = ctrNameAndFieldTypes c
reviewerIdClause :: Con -> ClauseQ
reviewerIdClause con = do
let (dataConName, fieldTypes) = ctrNameAndFieldTypes con
varNames <- for [0 .. length fieldTypes - 1] $ \i ->
newName ('x' : show i)
clause [conP dataConName (fmap varP varNames)]
(normalB (appE (conE 'Left) (conE dataConName `appsE1` fmap varE varNames)))
[]
freshMap :: Set Name -> Q (Map Name Name)
freshMap ns = Map.fromList <$> for (toList ns) (\ n -> (,) n <$> newName (nameBase n))
makeIsoFrom :: Type -> Name -> Q ([Name], Exp)
makeIsoFrom ty conName = lam <$> deCom ty
where
lam (ns, e) = (ns, LamE [TupP (map VarP ns)] e)
deCom (TupleT _) = return ([], ConE conName)
deCom (AppT l _) = do
(ln, l') <- deCom l
x <- newName "x"
return (ln ++ [x], AppE l' (VarE x))
deCom t = fail $ "unable to create isomorphism for: " ++ show t
makeIsoTo :: [Name] -> Name -> ExpQ
makeIsoTo ns conName = lamE [conP conName (map varP ns)]
$ tupE $ map varE ns
makeIsoBody :: Name -> Exp -> Exp -> DecQ
makeIsoBody lensName f t = funD lensName [clause [] (normalB body) []] where
body = appsE [ varE 'iso
, return f
, return t
]
makeLensBody :: Name -> Exp -> Exp -> DecQ
makeLensBody lensName i o = do
f <- newName "f"
a <- newName "a"
funD lensName [clause [] (normalB (
lamE [varP f, varP a] $
appsE [ varE 'fmap
, return o
, varE f `appE` (return i `appE` varE a)
])) []]
plain :: TyVarBndr -> TyVarBndr
plain (KindedTV t _) = PlainTV t
plain (PlainTV t) = PlainTV t
apps :: Type -> [Type] -> Type
apps = Prelude.foldl AppT
makeLensesForDec :: LensRules -> Dec -> Q [Dec]
makeLensesForDec cfg decl = case makeDataDecl decl of
Just dataDecl -> makeLensesForCons cfg dataDecl
Nothing -> fail "makeLensesWith: Unsupported data type"
makeLensesForCons :: LensRules -> DataDecl -> Q [Dec]
makeLensesForCons cfg dataDecl = case constructors dataDecl of
[NormalC dataConName [( _,ty)]]
| cfg^.handleSingletons ->
makeIsoLenses cfg dataDecl dataConName Nothing [ty]
[RecC dataConName [(fld,_,ty)]]
| cfg^.handleSingletons ->
makeIsoLenses cfg dataDecl dataConName (Just fld) [ty]
_ | cfg^.singletonRequired ->
fail "makeLensesWith: A single-constructor single-argument data type is required"
| otherwise ->
makeFieldLenses cfg dataDecl
makeDataDecl :: Dec -> Maybe DataDecl
makeDataDecl dec = case deNewtype dec of
DataD ctx tyName args cons _ -> Just DataDecl
{ dataContext = ctx
, tyConName = Just tyName
, dataParameters = args
, fullType = apps $ ConT tyName
, constructors = cons
}
DataInstD ctx familyName args cons _ -> Just DataDecl
{ dataContext = ctx
, tyConName = Nothing
, dataParameters = map PlainTV vars
, fullType = \tys -> apps (ConT familyName) $
substType (Map.fromList $ zip vars tys) args
, constructors = cons
}
where
vars = toList $ setOf typeVars args
_ -> Nothing
data DataDecl = DataDecl
{ dataContext :: Cxt
, tyConName :: Maybe Name
, dataParameters :: [TyVarBndr]
, fullType :: [Type] -> Type
, constructors :: [Con]
}
makeIsoLenses :: LensRules
-> DataDecl
-> Name
-> Maybe Name
-> [Type]
-> Q [Dec]
makeIsoLenses cfg dataDecl dataConName maybeFieldName partTy = do
let tyArgs = map plain (dataParameters dataDecl)
m <- freshMap $ setOf typeVars tyArgs
let aty = List.foldl' AppT (TupleT $ length partTy) partTy
bty = substTypeVars m aty
sty = fullType dataDecl $ map (VarT . view name) tyArgs
tty = substTypeVars m sty
quantified = ForallT (tyArgs ++ substTypeVars m tyArgs)
(dataContext dataDecl ++ substTypeVars m (dataContext dataDecl))
maybeIsoName = mkName <$> view lensIso cfg (nameBase dataConName)
lensOnly = not $ cfg^.singletonIso
isoCon | lensOnly = ConT ''Lens
| otherwise = ConT ''Iso
isoCon' | lensOnly = ConT ''Lens'
| otherwise = ConT ''Iso'
makeBody | lensOnly = makeLensBody
| otherwise = makeIsoBody
isoDecls <- flip (maybe (return [])) maybeIsoName $ \isoName -> do
let decl = SigD isoName $ quantified $
if cfg^.simpleLenses || Map.null m
then isoCon' `apps` [sty,aty]
else isoCon `apps` [sty,tty,aty,bty]
(ns, f) <- makeIsoFrom aty dataConName
t <- makeIsoTo ns dataConName
body <- makeBody isoName t f
#ifndef INLINING
return $ if cfg^.generateSignatures then [decl, body] else [body]
#else
inlining <- inlinePragma isoName
return $ if cfg^.generateSignatures then [decl, body, inlining] else [body, inlining]
#endif
accessorDecls <- case mkName <$> (maybeFieldName >>= view lensField cfg . nameBase) of
jfn@(Just lensName)
| (jfn /= maybeIsoName) && (isNothing maybeIsoName || cfg^.singletonAndField) -> do
let decl = SigD lensName $ quantified $
if cfg^.simpleLenses || Map.null m
then isoCon' `apps` [sty,aty]
else isoCon `apps` [sty,tty,aty,bty]
(ns, f) <- makeIsoFrom aty dataConName
t <- makeIsoTo ns dataConName
body <- makeBody lensName t f
#ifndef INLINING
return $ if cfg^.generateSignatures then [decl, body] else [body]
#else
inlining <- inlinePragma lensName
return $ if cfg^.generateSignatures then [decl, body, inlining] else [body, inlining]
#endif
_ -> return []
return $ isoDecls ++ accessorDecls
makeFieldGetterBody :: Bool -> Name -> [(Con, [Name])] -> Maybe Name -> Q Dec
makeFieldGetterBody isFold lensName conList maybeMethodName
= case maybeMethodName of
Just methodName -> do
go <- newName "go"
let expr = infixApp (varE methodName) (varE '(Prelude..)) (varE go)
funD lensName [ clause [] (normalB expr) [funD go clauses] ]
Nothing -> funD lensName clauses
where
clauses = map buildClause conList
buildClause (con, fields) | isRecord con = do
f <- newName "_f"
vars <- for (con^..conNamedFields._1) $ \fld ->
if fld `List.elem` fields
then Just <$> newName ('_':(nameBase fld++""))
else return Nothing
let cpats = maybe wildP varP <$> vars
fvals = map (appE (varE f) . varE) (catMaybes vars)
conName = con^.name
fpat
| List.null fvals = wildP
| otherwise = varP f
expr
| not isFold && length fields /= 1
= appE (varE 'error) . litE . stringL
$ show lensName ++ ": expected a single matching field in " ++ show conName ++ ", found " ++ show (length fields)
| List.null fields
= [| coerce (pure ()) |]
| List.null fvals = [| coerce (pure ()) |]
| otherwise
= let add x y = [| $x *> $y |]
in [| coerce $(List.foldl1 add fvals) |]
clause [fpat, conP conName cpats] (normalB expr) []
buildClause (con, _fields) =
clause [wildP, recP (con^.name) []] (normalB [| coerce (pure ()) |]) []
isRecord :: Con -> Bool
isRecord RecC{} = True
isRecord NormalC{} = False
isRecord InfixC{} = False
isRecord (ForallC _ _ c) = isRecord c
makeFieldLensBody :: Bool -> Name -> [(Con, [Name])] -> Maybe Name -> Q Dec
makeFieldLensBody isTraversal lensName conList maybeMethodName = case maybeMethodName of
Just methodName -> do
go <- newName "go"
let expr = infixApp (varE methodName) (varE '(Prelude..)) (varE go)
funD lensName [ clause [] (normalB expr) [funD go clauses] ]
Nothing -> funD lensName clauses
where
clauses = map buildClause conList
buildClause (con, fields) | isRecord con = do
f <- newName "_f"
vars <- for (con^..conNamedFields._1) $ \fld ->
if fld `List.elem` fields
then Left <$> ((,) <$> newName ('_':(nameBase fld++"'")) <*> newName ('_':nameBase fld))
else Right <$> newName ('_':nameBase fld)
let cpats = map (varP . either fst id) vars
cvals = map (varE . either snd id) vars
fpats = map (varP . snd) $ lefts vars
fvals = map (appE (varE f) . varE . fst) $ lefts vars
conName = con^.name
recon = conE conName `appsE1` cvals
fpat
| List.null fields = wildP
| otherwise = varP f
expr
| not isTraversal && length fields /= 1
= appE (varE 'error) . litE . stringL
$ show lensName ++ ": expected a single matching field in " ++ show conName ++ ", found " ++ show (length fields)
| List.null fields
= appE (varE 'pure) recon
| otherwise
= let step Nothing r = Just $ infixE (Just $ lamE fpats recon) (varE '(<$>)) (Just r)
step (Just l) r = Just $ infixE (Just l) (varE '(<*>)) (Just r)
in fromJust $ List.foldl step Nothing fvals
clause [fpat, conP conName cpats] (normalB expr) []
buildClause (con, _fields) = do
let fieldCount = lengthOf conFields con
vars <- replicateM fieldCount (newName "x")
let conName = con^.name
expr
| isTraversal = [| pure $(conE conName `appsE1` map varE vars) |]
| otherwise = [| error errorMsg |]
where errorMsg = show lensName ++ ": non-record constructors require traversals to be generated"
clause [wildP, conP conName (map varP vars)] (normalB expr) []
makeFieldLenses :: LensRules
-> DataDecl
-> Q [Dec]
makeFieldLenses cfg dataDecl = do
let tyArgs = map plain $ dataParameters dataDecl
maybeLensClass = view lensClass cfg . nameBase =<< tyConName dataDecl
maybeClassName = fmap (^._1.to mkName) maybeLensClass
cons = constructors dataDecl
t <- newName "t"
a <- newName "a"
lensFields <- map (\xs -> (fst $ head xs, map snd xs))
. groupBy ((==) `on` fst) . sortBy (comparing fst)
. concat
<$> mapM (getLensFields $ view lensField cfg) cons
let varMultiSet = List.concatMap (toListOf (conFields._2.typeVars)) cons
varSet = Set.fromList $ map (view name) tyArgs
bodies <- for lensFields $ \(lensName, fields) -> do
let fieldTypes = map (view _3) fields
otherVars = varMultiSet List.\\ fieldTypes^..typeVars
(tyArgs', cty) <- unifyTypes tyArgs fieldTypes
m <- freshMap . Set.difference varSet $ Set.fromList otherVars
let aty | isJust maybeClassName = VarT t
| otherwise = fullType dataDecl $ map (VarT . view name) tyArgs'
bty = substTypeVars m aty
dty = substTypeVars m cty
s = setOf folded m
relevantBndr b = s^.contains (b^.name)
relevantCtx = not . Set.null . Set.intersection s . setOf typeVars
tvs = tyArgs' ++ filter relevantBndr (substTypeVars m tyArgs')
ctx = dataContext dataDecl
ps = filter relevantCtx (substTypeVars m ctx)
qs = case maybeClassName of
#if MIN_VERSION_template_haskell(2,10,0)
Just n | not (cfg^.createClass) -> AppT (ConT n) (VarT t) : (ctx ++ ps)
#else
Just n | not (cfg^.createClass) -> ClassP n [VarT t] : (ctx ++ ps)
#endif
| otherwise -> ps
_ -> ctx ++ ps
tvs' = case maybeClassName of
Just _ | not (cfg^.createClass) -> PlainTV t : tvs
| otherwise -> []
_ -> tvs
fieldMap = fromListWith (++) $ map (\(cn,fn,_) -> (cn, [fn])) fields
conList = map (\c -> (c, Map.findWithDefault [] (view name c) fieldMap)) cons
maybeMethodName = fmap (mkName . view _2) maybeLensClass
isTraversal <- do
let notSingular = filter ((/= 1) . length . snd) conList
showCon (c, fs) = pprint (c^.name) ++ " { " ++ intercalate ", " (map pprint fs) ++ " }"
case (cfg^.buildTraversals, cfg^.partialLenses) of
(True, True) -> fail "Cannot makeLensesWith both of the flags buildTraversals and partialLenses."
(False, True) -> return False
(True, False) | List.null notSingular -> return False
| otherwise -> return True
(False, False) | List.null notSingular -> return False
| otherwise -> fail . unlines $
[ "Cannot use 'makeLensesWith' with constructors that don't map just one field"
, "to a lens, without using either the buildTraversals or partialLenses flags."
, if length conList == 1
then "The following constructor failed this criterion for the " ++ pprint lensName ++ " lens:"
else "The following constructors failed this criterion for the " ++ pprint lensName ++ " lens:"
] ++ map showCon conList
let decl = SigD lensName
$ case cty of
ForallT innerTys innerCxt cty' ->
ForallT (tvs'++innerTys) (qs++innerCxt)
$ apps (ConT (if isTraversal then ''Fold else ''Getter)) [aty,cty']
_ ->
ForallT tvs' qs
$ if aty == bty && cty == dty || cfg^.simpleLenses || isJust maybeClassName
then apps (ConT (if isTraversal then ''Traversal' else ''Lens')) [aty,cty]
else apps (ConT (if isTraversal then ''Traversal else ''Lens)) [aty,bty,cty,dty]
body <- case cty of
ForallT {} -> makeFieldGetterBody isTraversal lensName conList maybeMethodName
_ -> makeFieldLensBody isTraversal lensName conList maybeMethodName
#ifndef INLINING
return $ if cfg^.generateSignatures then [decl, body] else [body]
#else
inlining <- inlinePragma lensName
return $ if cfg^.generateSignatures then [decl, body, inlining] else [body, inlining]
#endif
let defs = Prelude.concat bodies
case maybeLensClass of
Nothing -> return defs
Just (clsNameString, methodNameString) -> do
let clsName = mkName clsNameString
methodName = mkName methodNameString
varArgs = varT . view name <$> tyArgs
appliedCon = fullType dataDecl <$> sequenceA varArgs
Prelude.sequence $
filter (\_ -> cfg^.createClass) [
classD (return []) clsName (PlainTV t : tyArgs) (if List.null tyArgs then [] else [FunDep [t] (view name <$> tyArgs)]) (
sigD methodName (appsT (conT ''Lens') [varT t, appliedCon]) :
map return defs)]
++ filter (\_ -> cfg^.createInstance) [
instanceD (return []) ((conT clsName `appT` appliedCon) `appsT` varArgs) [
funD methodName [clause [varP a] (normalB (varE a)) []]
#ifdef INLINING
, inlinePragma methodName
#endif
]]
++ filter (\_ -> not $ cfg^.createClass) (map return defs)
getLensFields :: (String -> Maybe String) -> Con -> Q [(Name, (Name, Name, Type))]
getLensFields f (RecC cn fs)
= return . catMaybes
$ fs <&> \(fn,_,t) -> f (nameBase fn) <&> \ln -> (mkName ln, (cn,fn,t))
getLensFields f (ForallC tvs cxts con) = fmap (filter p) (getLensFields f con)
where
prohibitedTypes = tvs^..typeVars ++ cxts^..typeVars
p field = not (any (\t -> elemOf (_2._3.typeVars) t field) prohibitedTypes)
getLensFields _ _
= return []
unifyTypes :: [TyVarBndr] -> [Type] -> Q ([TyVarBndr], Type)
unifyTypes tvs tys = return (tvs, head tys)
makeWrapped :: Name -> DecsQ
makeWrapped nm = do
inf <- reify nm
case inf of
TyConI decl -> do
maybeDecs <- makeWrappedForDec decl
maybe (fail "makeWrapped: Unsupported data type") return maybeDecs
_ -> fail "makeWrapped: Expected the name of a newtype or datatype"
makeWrappedForDec :: Dec -> Q (Maybe [Dec])
makeWrappedForDec decl = case makeDataDecl decl of
Just dataDecl | [con] <- constructors dataDecl
, [field] <- toListOf (conFields._2) con
-> do wrapped <- makeWrappedInstance dataDecl con field
rewrapped <- makeRewrappedInstance dataDecl
return (Just [rewrapped, wrapped])
_ -> return Nothing
makeRewrappedInstance :: DataDecl -> DecQ
makeRewrappedInstance dataDecl = do
t <- varT <$> newName "t"
let typeArgs = map (view name) (dataParameters dataDecl)
typeArgs' <- do
m <- freshMap (Set.fromList typeArgs)
return (substTypeVars m typeArgs)
let appliedType = return (fullType dataDecl (map VarT typeArgs))
appliedType' = return (fullType dataDecl (map VarT typeArgs'))
#if MIN_VERSION_template_haskell(2,10,0)
eq = AppT. AppT EqualityT <$> appliedType' <*> t
#else
eq = equalP appliedType' t
#endif
klass = conT ''Rewrapped `appsT` [appliedType, t]
instanceD (cxt [eq]) klass []
makeWrappedInstance :: DataDecl-> Con -> Type -> DecQ
makeWrappedInstance dataDecl con fieldType = do
let conName = view name con
let typeArgs = toListOf typeVars (dataParameters dataDecl)
let appliedType = fullType dataDecl (map VarT typeArgs)
let unwrappedATF = tySynInstD' ''Unwrapped [return appliedType] (return fieldType)
let klass = conT ''Wrapped `appT` return appliedType
let wrapFun = conE conName
let unwrapFun = newName "x" >>= \x -> lam1E (conP conName [varP x]) (varE x)
let isoMethod = funD '_Wrapped' [clause [] (normalB [|iso $unwrapFun $wrapFun|]) []]
instanceD (cxt []) klass [unwrappedATF, isoMethod]
#if !(MIN_VERSION_template_haskell(2,7,0))
instance Applicative Q where
pure = return
(<*>) = ap
#endif
#ifdef INLINING
inlinePragma :: Name -> Q Dec
#if MIN_VERSION_template_haskell(2,8,0)
# ifdef OLD_INLINE_PRAGMAS
inlinePragma methodName = pragInlD methodName $ inlineSpecNoPhase Inline False
# else
inlinePragma methodName = pragInlD methodName Inline FunLike AllPhases
# endif
#else
inlinePragma methodName = pragInlD methodName $ inlineSpecNoPhase True False
#endif
#endif
data FieldRules = FieldRules
{ _getPrefix :: [String] -> String -> Maybe String
, _rawLensNaming :: String -> String
, _niceLensNaming :: String -> Maybe String
, _classNaming :: String -> Maybe String
}
data Field = Field
{ _fieldName :: Name
, _fieldLensPrefix :: String
, _fieldLensName :: Name
, _fieldClassName :: Name
, _fieldClassLensName :: Name
, _fieldNameType :: Type
}
overHead :: (a -> a) -> [a] -> [a]
overHead _ [] = []
overHead f (x:xs) = f x : xs
underscoreFields :: FieldRules
underscoreFields = FieldRules prefix rawLens niceLens classNaming
where
prefix _ ('_':xs) | '_' `List.elem` xs = Just (takeWhile (/= '_') xs)
prefix _ _ = Nothing
rawLens x = x ++ "_lens"
niceLens x = prefix [] x <&> \n -> drop (length n + 2) x
classNaming x = niceLens x <&> ("Has_" ++)
camelCaseFields :: FieldRules
camelCaseFields = FieldRules prefix rawLens niceLens classNaming
where
sepUpper x = case break isUpper x of
(p, s) | List.null p || List.null s -> Nothing
| otherwise -> Just (p,s)
prefix fields = fmap fst . sepUpper <=< dealWith_ fields
rawLens x = x ++ "Lens"
niceLens x = overHead toLower . snd <$> sepUpper x
classNaming x = niceLens x <&> \ (n:ns) -> "Has" ++ toUpper n : ns
dealWith_ :: [String] -> String -> Maybe String
dealWith_ fields field | not $ any (fst . leading_) fields = Just field
| otherwise = if leading then Just trailing else Nothing
where
leading_ ('_':xs) = (True, xs)
leading_ xs = (False, xs)
(leading, trailing) = leading_ field
collectRecords :: [Con] -> [VarStrictType]
collectRecords cons = nubBy varEq allRecordFields
where
varEq (name1,_,_) (name2,_,_) = name1 == name2
allRecordFields = [ field | RecC _ fields <- cons , field <- fields ]
verboseLenses :: FieldRules -> Dec -> Q [Dec]
verboseLenses c decl = do
cons <- case deNewtype decl of
DataD _ _ _ cons _ -> return cons
DataInstD _ _ _ cons _ -> return cons
_ -> fail "verboseLenses: Unsupported data type"
let rs = collectRecords cons
if List.null rs
then fail "verboseLenses: Expected the name of a record type"
else flip makeLenses' decl
$ mkFields c rs
& map (\(Field n _ l _ _ _) -> (show n, show l))
where
makeLenses' fields' =
makeLensesForDec $ lensRules
& lensField .~ (`Prelude.lookup` fields')
& buildTraversals .~ False
& partialLenses .~ True
mkFields :: FieldRules -> [VarStrictType] -> [Field]
mkFields (FieldRules prefix' raw' nice' clas') rs
= Maybe.mapMaybe namer fieldNamesAndTypes
& List.groupBy (on (==) _fieldLensPrefix)
& (\ gs -> case gs of
x:_ -> x
_ -> [])
where
fieldNamesAndTypes = [(nameBase n, t) | (n,_,t) <- rs]
fieldNames = map fst fieldNamesAndTypes
namer (field, fieldType) = do
let rawlens = mkName (raw' field)
prefix <- prefix' fieldNames field
nice <- mkName <$> nice' field
clas <- mkName <$> clas' field
return (Field (mkName field) prefix rawlens clas nice fieldType)
hasClassAndInstance :: FieldRules -> Dec -> Q [Dec]
hasClassAndInstance cfg decl = do
c <- newName "c"
e <- newName "e"
dataDecl <- case makeDataDecl decl of
Just dataDecl -> return dataDecl
_ -> fail "hasClassAndInstance: Unsupported data type"
let rs = collectRecords $ constructors dataDecl
when (List.null rs) $
fail "hasClassAndInstance: Expected the name of a record type"
fmap concat . forM (mkFields cfg rs) $ \(Field _ _ fullLensName className lensName fieldType) -> do
classHas <- classD
(return [])
className
[ PlainTV c, PlainTV e ]
[ FunDep [c] [e] ]
[ sigD lensName (conT ''Lens' `appsT` [varT c, varT e])]
instanceHas <- instanceD
(return [])
(return $ ConT className `apps`
[fullType dataDecl $ map (VarT . view name) (dataParameters dataDecl)
, fieldType])
[
#ifdef INLINING
inlinePragma lensName,
#endif
funD lensName [ clause [] (normalB (varE fullLensName)) [] ]
]
classAlreadyExists <- isJust `fmap` lookupTypeName (show className)
return (if classAlreadyExists then [instanceHas] else [classHas, instanceHas])
makeFieldsWith :: FieldRules -> Name -> Q [Dec]
makeFieldsWith c n = do
inf <- reify n
case inf of
TyConI decl -> makeFieldsForDec c decl
_ -> fail "makeFieldsWith: Expected the name of a data type or newtype"
makeFieldsForDec :: FieldRules -> Dec -> Q [Dec]
makeFieldsForDec cfg decl = liftA2 (++)
(verboseLenses cfg decl)
(hasClassAndInstance cfg decl)
makeFields :: Name -> Q [Dec]
makeFields = makeFieldsWith defaultFieldRules
defaultFieldRules :: FieldRules
defaultFieldRules = camelCaseFields
declareWith :: (Dec -> Declare Dec) -> Q [Dec] -> Q [Dec]
declareWith fun = (runDeclare . traverseDataAndNewtype fun =<<)
type Declare = WriterT (Endo [Dec]) Q
runDeclare :: Declare [Dec] -> Q [Dec]
runDeclare dec = do
(out, endo) <- runWriterT dec
return $ out ++ appEndo endo []
emit :: [Dec] -> Declare ()
emit decs = tell $ Endo (decs++)
traverseDataAndNewtype :: (Applicative f) => (Dec -> f Dec) -> [Dec] -> f [Dec]
traverseDataAndNewtype f decs = traverse go decs
where
go dec = case dec of
DataD{} -> f dec
NewtypeD{} -> f dec
DataInstD{} -> f dec
NewtypeInstD{} -> f dec
InstanceD ctx inst body -> InstanceD ctx inst <$> traverse go body
_ -> pure dec
stripFields :: Dec -> Dec
stripFields dec = case dec of
DataD ctx tyName tyArgs cons derivings ->
DataD ctx tyName tyArgs (map deRecord cons) derivings
NewtypeD ctx tyName tyArgs con derivings ->
NewtypeD ctx tyName tyArgs (deRecord con) derivings
DataInstD ctx tyName tyArgs cons derivings ->
DataInstD ctx tyName tyArgs (map deRecord cons) derivings
NewtypeInstD ctx tyName tyArgs con derivings ->
NewtypeInstD ctx tyName tyArgs (deRecord con) derivings
_ -> dec
deRecord :: Con -> Con
deRecord con@NormalC{} = con
deRecord con@InfixC{} = con
deRecord (ForallC tyVars ctx con) = ForallC tyVars ctx $ deRecord con
deRecord (RecC conName fields) = NormalC conName (map dropFieldName fields)
where dropFieldName (_, str, typ) = (str, typ)