{-# LANGUAGE LambdaCase, TemplateHaskell, TypeFamilies, DeriveFunctor #-} module Data.Extensible.Record (IsRecord(..), deriveIsRecord) where import Language.Haskell.TH import Data.Extensible.Internal import Data.Extensible.Product import Data.Extensible.Field import Data.Functor.Identity import GHC.TypeLits -- | The class of types that can be converted to/from a 'Record'. class IsRecord a where type RecFields a :: [Assoc Symbol *] fromRecord :: Record (RecFields a) -> a toRecord :: a -> Record (RecFields a) tvName :: TyVarBndr -> Name tvName (PlainTV n) = n tvName (KindedTV n _) = n deriveIsRecord :: Name -> DecsQ deriveIsRecord name = reify name >>= \case #if MIN_VERSION_template_haskell(2,11,0) TyConI (DataD _ _ vars _ [RecC conName vst] _) -> do #else TyConI (DataD _ _ vars [RecC conName vst] _) -> do #endif rec <- newName "rec" let names = [x | (x, _, _) <- vst] newNames <- traverse (newName . nameBase) names let tvmap = [(tvName tv, VarT (mkName $ "p" ++ show i)) | (i, tv) <- zip [0 :: Int ..] vars] let ty = foldl AppT (ConT name) $ map snd tvmap let refineTV (VarT t) | Just t' <- lookup t tvmap = t' refineTV (AppT a b) = refineTV a `AppT` refineTV b refineTV t = t return #if MIN_VERSION_template_haskell(2,11,0) [InstanceD Nothing [] (ConT ''IsRecord `AppT` ty) #else [InstanceD [] (ConT ''IsRecord `AppT` ty) #endif [ TySynInstD ''RecFields $ TySynEqn [ty] $ foldr (\(v, _, t) r -> PromotedConsT `AppT` (PromotedT '(:>) `AppT` LitT (StrTyLit $ nameBase v) `AppT` refineTV t) `AppT` r) PromotedNilT vst , FunD 'fromRecord [Clause [shape2Pat $ fmap (\x -> ConP 'Field [ConP 'Identity [VarP x]]) $ foldr consShape SNil newNames] (NormalB $ RecConE conName [(n, VarE n') | (n, n') <- zip names newNames]) [] ] , FunD 'toRecord [Clause [VarP rec] (NormalB $ shape2Exp $ foldr consShape SNil [AppE (ConE 'Field) $ AppE (ConE 'Identity) $ VarE n `AppE` VarE rec | n <- names]) [] ] ] ] info -> fail $ "deriveAsRecord: Unsupported " ++ show info shape2Pat :: Shape Pat -> Pat shape2Pat SNil = ConP 'Nil [] shape2Pat (STree p l r) = ConP 'Tree [p, shape2Pat l, shape2Pat r] shape2Exp :: Shape Exp -> Exp shape2Exp SNil = ConE 'Nil shape2Exp (STree e l r) = ConE 'Tree `AppE` e `AppE` shape2Exp l `AppE` shape2Exp r data Shape a = SNil | STree a (Shape a) (Shape a) deriving Functor consShape :: a -> Shape a -> Shape a consShape a SNil = STree a SNil SNil consShape a (STree b l r) = STree a (consShape b r) l