{-# LANGUAGE TemplateHaskellQuotes #-} {-# LANGUAGE Unsafe #-} {-| Module: Data.Bifunctor.TH.Internal Copyright: (C) 2008-2016 Edward Kmett, (C) 2015-2016 Ryan Scott License: BSD-style (see the file LICENSE) Maintainer: Edward Kmett Portability: Template Haskell Template Haskell-related utilities. -} module Data.Bifunctor.TH.Internal where import Control.Applicative import Data.Bifunctor (Bifunctor(..)) import Data.Bifoldable (Bifoldable(..)) import Data.Bitraversable (Bitraversable(..)) import Data.Coerce (coerce) import Data.Foldable (foldr') import qualified Data.List as List import qualified Data.Map as Map (singleton) import Data.Map (Map) import Data.Maybe (fromMaybe, mapMaybe) import Data.Monoid (Dual(..), Endo(..)) import qualified Data.Set as Set import Data.Set (Set) import Language.Haskell.TH.Datatype import Language.Haskell.TH.Lib import Language.Haskell.TH.Syntax ------------------------------------------------------------------------------- -- Expanding type synonyms ------------------------------------------------------------------------------- applySubstitutionKind :: Map Name Kind -> Type -> Type applySubstitutionKind = applySubstitution substNameWithKind :: Name -> Kind -> Type -> Type substNameWithKind n k = applySubstitutionKind (Map.singleton n k) substNamesWithKindStar :: [Name] -> Type -> Type substNamesWithKindStar ns t = foldr' (flip substNameWithKind starK) t ns ------------------------------------------------------------------------------- -- Type-specialized const functions ------------------------------------------------------------------------------- bimapConst :: p b d -> (a -> b) -> (c -> d) -> p a c -> p b d bimapConst = const . const . const {-# INLINE bimapConst #-} bifoldrConst :: c -> (a -> c -> c) -> (b -> c -> c) -> c -> p a b -> c bifoldrConst = const . const . const . const {-# INLINE bifoldrConst #-} bifoldMapConst :: m -> (a -> m) -> (b -> m) -> p a b -> m bifoldMapConst = const . const . const {-# INLINE bifoldMapConst #-} bitraverseConst :: f (t c d) -> (a -> f c) -> (b -> f d) -> t a b -> f (t c d) bitraverseConst = const . const . const {-# INLINE bitraverseConst #-} ------------------------------------------------------------------------------- -- StarKindStatus ------------------------------------------------------------------------------- -- | Whether a type is not of kind *, is of kind *, or is a kind variable. data StarKindStatus = NotKindStar | KindStar | IsKindVar Name deriving Eq -- | Does a Type have kind * or k (for some kind variable k)? canRealizeKindStar :: Type -> StarKindStatus canRealizeKindStar t | hasKindStar t = KindStar | otherwise = case t of SigT _ (VarT k) -> IsKindVar k _ -> NotKindStar -- | Returns 'Just' the kind variable 'Name' of a 'StarKindStatus' if it exists. -- Otherwise, returns 'Nothing'. starKindStatusToName :: StarKindStatus -> Maybe Name starKindStatusToName (IsKindVar n) = Just n starKindStatusToName _ = Nothing -- | Concat together all of the StarKindStatuses that are IsKindVar and extract -- the kind variables' Names out. catKindVarNames :: [StarKindStatus] -> [Name] catKindVarNames = mapMaybe starKindStatusToName ------------------------------------------------------------------------------- -- Assorted utilities ------------------------------------------------------------------------------- -- filterByList, filterByLists, and partitionByList taken from GHC (BSD3-licensed) -- | 'filterByList' takes a list of Bools and a list of some elements and -- filters out these elements for which the corresponding value in the list of -- Bools is False. This function does not check whether the lists have equal -- length. filterByList :: [Bool] -> [a] -> [a] filterByList (True:bs) (x:xs) = x : filterByList bs xs filterByList (False:bs) (_:xs) = filterByList bs xs filterByList _ _ = [] -- | 'filterByLists' takes a list of Bools and two lists as input, and -- outputs a new list consisting of elements from the last two input lists. For -- each Bool in the list, if it is 'True', then it takes an element from the -- former list. If it is 'False', it takes an element from the latter list. -- The elements taken correspond to the index of the Bool in its list. -- For example: -- -- @ -- filterByLists [True, False, True, False] \"abcd\" \"wxyz\" = \"axcz\" -- @ -- -- This function does not check whether the lists have equal length. filterByLists :: [Bool] -> [a] -> [a] -> [a] filterByLists (True:bs) (x:xs) (_:ys) = x : filterByLists bs xs ys filterByLists (False:bs) (_:xs) (y:ys) = y : filterByLists bs xs ys filterByLists _ _ _ = [] -- | 'partitionByList' takes a list of Bools and a list of some elements and -- partitions the list according to the list of Bools. Elements corresponding -- to 'True' go to the left; elements corresponding to 'False' go to the right. -- For example, @partitionByList [True, False, True] [1,2,3] == ([1,3], [2])@ -- This function does not check whether the lists have equal -- length. partitionByList :: [Bool] -> [a] -> ([a], [a]) partitionByList = go [] [] where go trues falses (True : bs) (x : xs) = go (x:trues) falses bs xs go trues falses (False : bs) (x : xs) = go trues (x:falses) bs xs go trues falses _ _ = (reverse trues, reverse falses) -- | Returns True if a Type has kind *. hasKindStar :: Type -> Bool hasKindStar VarT{} = True hasKindStar (SigT _ StarT) = True hasKindStar _ = False -- Returns True is a kind is equal to *, or if it is a kind variable. isStarOrVar :: Kind -> Bool isStarOrVar StarT = True isStarOrVar VarT{} = True isStarOrVar _ = False -- | @hasKindVarChain n kind@ Checks if @kind@ is of the form -- k_0 -> k_1 -> ... -> k_(n-1), where k0, k1, ..., and k_(n-1) can be * or -- kind variables. hasKindVarChain :: Int -> Type -> Maybe [Name] hasKindVarChain kindArrows t = let uk = uncurryKind (tyKind t) in if (length uk - 1 == kindArrows) && all isStarOrVar uk then Just (freeVariables uk) else Nothing -- | If a Type is a SigT, returns its kind signature. Otherwise, return *. tyKind :: Type -> Kind tyKind (SigT _ k) = k tyKind _ = starK -- | A mapping of type variable Names to their map function Names. For example, in a -- Bifunctor declaration, a TyVarMap might look like (a ~> f, b ~> g), where -- a and b are the last two type variables of the datatype, and f and g are the two -- functions which map their respective type variables. type TyVarMap = Map Name Name thd3 :: (a, b, c) -> c thd3 (_, _, c) = c unsnoc :: [a] -> Maybe ([a], a) unsnoc [] = Nothing unsnoc (x:xs) = case unsnoc xs of Nothing -> Just ([], x) Just (a,b) -> Just (x:a, b) -- | Generate a list of fresh names with a common prefix, and numbered suffixes. newNameList :: String -> Int -> Q [Name] newNameList prefix n = mapM (newName . (prefix ++) . show) [1..n] -- | Applies a typeclass constraint to a type. applyClass :: Name -> Name -> Pred applyClass con t = AppT (ConT con) (VarT t) -- | Checks to see if the last types in a data family instance can be safely eta- -- reduced (i.e., dropped), given the other types. This checks for three conditions: -- -- (1) All of the dropped types are type variables -- (2) All of the dropped types are distinct -- (3) None of the remaining types mention any of the dropped types canEtaReduce :: [Type] -> [Type] -> Bool canEtaReduce remaining dropped = all isTyVar dropped && allDistinct droppedNames -- Make sure not to pass something of type [Type], since Type -- didn't have an Ord instance until template-haskell-2.10.0.0 && not (any (`mentionsName` droppedNames) remaining) where droppedNames :: [Name] droppedNames = map varTToName dropped -- | Extract Just the Name from a type variable. If the argument Type is not a -- type variable, return Nothing. varTToName_maybe :: Type -> Maybe Name varTToName_maybe (VarT n) = Just n varTToName_maybe (SigT t _) = varTToName_maybe t varTToName_maybe _ = Nothing -- | Extract the Name from a type variable. If the argument Type is not a -- type variable, throw an error. varTToName :: Type -> Name varTToName = fromMaybe (error "Not a type variable!") . varTToName_maybe -- | Peel off a kind signature from a Type (if it has one). unSigT :: Type -> Type unSigT (SigT t _) = t unSigT t = t -- | Is the given type a variable? isTyVar :: Type -> Bool isTyVar (VarT _) = True isTyVar (SigT t _) = isTyVar t isTyVar _ = False -- | Detect if a Name in a list of provided Names occurs as an argument to some -- type family. This makes an effort to exclude /oversaturated/ arguments to -- type families. For instance, if one declared the following type family: -- -- @ -- type family F a :: Type -> Type -- @ -- -- Then in the type @F a b@, we would consider @a@ to be an argument to @F@, -- but not @b@. isInTypeFamilyApp :: [Name] -> Type -> [Type] -> Q Bool isInTypeFamilyApp names tyFun tyArgs = case tyFun of ConT tcName -> go tcName _ -> return False where go :: Name -> Q Bool go tcName = do info <- reify tcName case info of FamilyI (OpenTypeFamilyD (TypeFamilyHead _ bndrs _ _)) _ -> withinFirstArgs bndrs FamilyI (ClosedTypeFamilyD (TypeFamilyHead _ bndrs _ _) _) _ -> withinFirstArgs bndrs _ -> return False where withinFirstArgs :: [a] -> Q Bool withinFirstArgs bndrs = let firstArgs = take (length bndrs) tyArgs argFVs = freeVariables firstArgs in return $ any (`elem` argFVs) names -- | Are all of the items in a list (which have an ordering) distinct? -- -- This uses Set (as opposed to nub) for better asymptotic time complexity. allDistinct :: Ord a => [a] -> Bool allDistinct = allDistinct' Set.empty where allDistinct' :: Ord a => Set a -> [a] -> Bool allDistinct' uniqs (x:xs) | x `Set.member` uniqs = False | otherwise = allDistinct' (Set.insert x uniqs) xs allDistinct' _ _ = True -- | Does the given type mention any of the Names in the list? mentionsName :: Type -> [Name] -> Bool mentionsName = go where go :: Type -> [Name] -> Bool go (AppT t1 t2) names = go t1 names || go t2 names go (SigT t k) names = go t names || go k names go (VarT n) names = n `elem` names go _ _ = False -- | Does an instance predicate mention any of the Names in the list? predMentionsName :: Pred -> [Name] -> Bool predMentionsName = mentionsName -- | Construct a type via curried application. applyTy :: Type -> [Type] -> Type applyTy = List.foldl' AppT -- | Fully applies a type constructor to its type variables. applyTyCon :: Name -> [Type] -> Type applyTyCon = applyTy . ConT -- | Split an applied type into its individual components. For example, this: -- -- @ -- Either Int Char -- @ -- -- would split to this: -- -- @ -- [Either, Int, Char] -- @ unapplyTy :: Type -> (Type, [Type]) unapplyTy ty = go ty ty [] where go :: Type -> Type -> [Type] -> (Type, [Type]) go _ (AppT ty1 ty2) args = go ty1 ty1 (ty2:args) go origTy (SigT ty' _) args = go origTy ty' args go origTy (InfixT ty1 n ty2) args = go origTy (ConT n `AppT` ty1 `AppT` ty2) args go origTy (ParensT ty') args = go origTy ty' args go origTy _ args = (origTy, args) -- | Split a type signature by the arrows on its spine. For example, this: -- -- @ -- forall a b. (a ~ b) => (a -> b) -> Char -> () -- @ -- -- would split to this: -- -- @ -- (a ~ b, [a -> b, Char, ()]) -- @ uncurryTy :: Type -> (Cxt, [Type]) uncurryTy (AppT (AppT ArrowT t1) t2) = let (ctxt, tys) = uncurryTy t2 in (ctxt, t1:tys) uncurryTy (SigT t _) = uncurryTy t uncurryTy (ForallT _ ctxt t) = let (ctxt', tys) = uncurryTy t in (ctxt ++ ctxt', tys) uncurryTy t = ([], [t]) -- | Like uncurryType, except on a kind level. uncurryKind :: Kind -> [Kind] uncurryKind = snd . uncurryTy ------------------------------------------------------------------------------- -- Quoted names ------------------------------------------------------------------------------- bimapConstValName :: Name bimapConstValName = 'bimapConst bifoldrConstValName :: Name bifoldrConstValName = 'bifoldrConst bifoldMapConstValName :: Name bifoldMapConstValName = 'bifoldMapConst coerceValName :: Name coerceValName = 'coerce bitraverseConstValName :: Name bitraverseConstValName = 'bitraverseConst wrapMonadDataName :: Name wrapMonadDataName = 'WrapMonad functorTypeName :: Name functorTypeName = ''Functor foldableTypeName :: Name foldableTypeName = ''Foldable traversableTypeName :: Name traversableTypeName = ''Traversable composeValName :: Name composeValName = '(.) idValName :: Name idValName = 'id errorValName :: Name errorValName = 'error flipValName :: Name flipValName = 'flip fmapValName :: Name fmapValName = 'fmap foldrValName :: Name foldrValName = 'foldr foldMapValName :: Name foldMapValName = 'foldMap seqValName :: Name seqValName = 'seq traverseValName :: Name traverseValName = 'traverse unwrapMonadValName :: Name unwrapMonadValName = 'unwrapMonad bifunctorTypeName :: Name bifunctorTypeName = ''Bifunctor bimapValName :: Name bimapValName = 'bimap pureValName :: Name pureValName = 'pure apValName :: Name apValName = '(<*>) liftA2ValName :: Name liftA2ValName = 'liftA2 mappendValName :: Name mappendValName = 'mappend memptyValName :: Name memptyValName = 'mempty bifoldableTypeName :: Name bifoldableTypeName = ''Bifoldable bitraversableTypeName :: Name bitraversableTypeName = ''Bitraversable bifoldrValName :: Name bifoldrValName = 'bifoldr bifoldMapValName :: Name bifoldMapValName = 'bifoldMap bitraverseValName :: Name bitraverseValName = 'bitraverse appEndoValName :: Name appEndoValName = 'appEndo dualDataName :: Name dualDataName = 'Dual endoDataName :: Name endoDataName = 'Endo getDualValName :: Name getDualValName = 'getDual