module Data.DeepLenses where import Control.Lens (Lens', makeClassy) import Language.Haskell.TH import Language.Haskell.TH.Datatype ( ConstructorInfo(ConstructorInfo), ConstructorVariant(RecordConstructor), DatatypeInfo(datatypeCons, datatypeName), reifyDatatype, ) import Language.Haskell.TH.Syntax (ModName(..), Name(Name), NameFlavour(NameQ, NameS, NameG), OccName(..)) class DeepLenses s s' where deepLens :: Lens' s s' data Field = Field { fieldName :: Name, fieldType :: Type } deriving Show data DT = DT { dtName :: Name, dtFields :: [Field] } deriving Show dataType :: Name -> Q DT dataType name = do info <- reifyDatatype name return $ DT (datatypeName info) (fields $ datatypeCons info) where fields [ConstructorInfo _ _ _ types _ (RecordConstructor names)] = zipWith Field names types fields _ = [] mkHoist :: TypeQ -> TypeQ -> BodyQ -> DecQ mkHoist _ _ body = do (VarE name) <- [|deepLens|] funD name [clause [] body []] deepLensesInstance :: TypeQ -> TypeQ -> BodyQ -> DecQ deepLensesInstance top local' body = instanceD (cxt []) (appT (appT [t|DeepLenses|] top) local') [mkHoist top local' body] idLenses :: Name -> DecQ idLenses name = deepLensesInstance nt nt body where nt = conT name body = normalB [|id|] eligibleForDeepError :: Name -> Q Bool eligibleForDeepError tpe = do (ConT name) <- [t|DeepLenses|] isInstance name [ConT tpe, ConT tpe] modName :: NameFlavour -> Maybe ModName modName (NameQ mod') = Just mod' modName (NameG _ _ mod') = Just mod' modName _ = Nothing sameModule :: NameFlavour -> NameFlavour -> Bool sameModule f1 f2 = case (modName f1, modName f2) of (Just a, Just b) | a == b -> True _ -> False lensName :: Name -> Name -> ExpQ lensName (Name _ topFlavour) (Name (OccName n) lensFlavour) = varE (Name (OccName (lensNams' n)) flavour) where lensNams' ('_' : t) = t lensNams' [] = [] lensNams' a = a flavour | sameModule topFlavour lensFlavour = NameS | otherwise = lensFlavour fieldLenses :: Name -> [Name] -> Field -> DecsQ fieldLenses top intermediate (Field name (ConT tpe)) = do current <- deepLensesInstance (conT top) (conT tpe) (normalB body) sub <- dataLensesIfEligible top (name : intermediate) tpe return (current : sub) where compose = appE . appE [|(.)|] . lensName top body = foldr compose (lensName top name) (reverse intermediate) fieldLenses _ _ _ = return [] dataLenses :: Name -> [Name] -> Name -> DecsQ dataLenses top intermediate local' = do (DT _ fields) <- dataType local' join <$> traverse (fieldLenses top intermediate) fields dataLensesIfEligible :: Name -> [Name] -> Name -> DecsQ dataLensesIfEligible top intermediate local' = do eligible <- eligibleForDeepError local' if eligible then dataLenses top intermediate local' else return [] lensesForMainData :: Name -> DecsQ lensesForMainData name = do idL <- idLenses name fields <- dataLenses name [] name return (idL : fields) deepLenses :: Name -> DecsQ deepLenses name = do lenses <- makeClassy name err <- lensesForMainData name return $ lenses ++ err