type-machine-0.1.0.0: src/TypeMachine/Type.hs
module TypeMachine.Type (
Type (..),
getField,
hasField,
typeToDec,
decToType,
reifyType,
) where
import Data.Bifunctor (first)
import Data.Functor ((<&>))
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Language.Haskell.TH.Syntax hiding (Type, reifyType)
import qualified Language.Haskell.TH.Syntax as TH (Type)
import TypeMachine.TH.Internal.Utils
-- | Data structure to easily manipulate Template Haskell's 'Language.Haskell.TH.Dec.DataD'
data Type = Type
{ name :: Name
-- ^ Name of the data type
, fields :: Map String BangType
-- ^ Fields of the data type
, typeParams :: [(String, Maybe Kind)]
-- ^ Type parameter of the ADT
}
deriving (Show, Eq)
getField :: String -> Type -> Maybe (Bang, TH.Type)
getField fieldName ty = Map.lookup fieldName (fields ty)
hasField :: String -> Type -> Bool
hasField n t = Map.member n $ fields t
-- | Turns a 'Type' back to a Template Haskell 'Language.Haskell.TH.Dec'
typeToDec :: Type -> Dec
typeToDec (Type n fs tp) =
DataD
[]
n
(tpToTyVarBndrs . first mkName <$> tp)
Nothing
[RecC (mkName $ nameBase n) $ fieldsToVbt fs]
[]
where
tpToTyVarBndrs (varName, Nothing) = PlainTV varName BndrInvis
tpToTyVarBndrs (varName, Just k) = KindedTV varName BndrInvis k
-- | Transform a Template Haskell 'Dec' into a TypeMachine's type
--
-- For this to succeed, the input type must have exactly one record constructor
decToType :: (Quasi m) => Dec -> m Type
decToType dec = do
(tyName, tyBndrs, cons) <- case dec of
(TySynD _ _ (ConT tyconsName)) -> do
tyconsInfo <- runQ $ reify tyconsName
case tyconsInfo of
TyConI dec' -> getTyInfo dec'
_ -> fail failMsg
_ -> getTyInfo dec
vbt <- getRecordConstructorVars cons
let tparams =
tyBndrs <&> \case
PlainTV n _ -> (n, Nothing)
KindedTV n _ k -> (n, Just k)
return $ Type tyName (vbtToFields vbt) (first nameBase <$> tparams)
where
failMsg = "Unsupported data type: Expected record type with exactly one constructor"
getTyInfo decl = case decl of
(DataD _ tyName tybndrs _ cons _) -> return (tyName, tybndrs, cons)
(NewtypeD _ tyName tybndrs _ con _) -> return (tyName, tybndrs, [con])
_ -> fail failMsg
-- | Wrapper around the TH's 'reify' function. Fails if the type is not a datatype declaration
reifyType :: Name -> Q Type
reifyType n = do
res <- reify n
case res of
TyConI ty -> decToType ty
_ -> fail "Invalid Name. Expected Datatype declaration"
{-# INLINE fieldsToVbt #-}
fieldsToVbt :: Map String BangType -> [VarBangType]
fieldsToVbt = Map.foldrWithKey (\key (b, t) list -> (mkName key, b, t) : list) []
{-# INLINE vbtToFields #-}
vbtToFields :: [VarBangType] -> Map String BangType
vbtToFields = Map.fromList . fmap (\(n, b, t) -> (nameBase n, (b, t)))