th-context-0.13: Language/Haskell/TH/Context.hs
-- | Compute whether any existing instance satisfies some
-- context in a nearly correct fashion.
-- @
-- instance A m => B m where ...
-- @
-- I say "nearly correct" because there are cases which are
-- not handled exactly the way GHC behaves, which may lead to
-- false (positives? negatives?)
{-# LANGUAGE CPP, DeriveDataTypeable, FlexibleContexts, MultiParamTypeClasses, ScopedTypeVariables, StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}
module Language.Haskell.TH.Context
( InstMap
, reifyInstancesWithContext
, tellInstance
-- * State/writer monad runners
, evalContextState
, execContextWriter
, execContext
, runContext
) where
#if __GLASGOW_HASKELL__ < 709
import Control.Applicative ((<$>), (<*>))
import Data.Monoid (mempty)
#else
import Data.Maybe (isJust)
#endif
import Control.Monad (filterM)
import Control.Monad.State (MonadState, StateT, get, modify, evalStateT)
import Control.Monad.Writer (MonadWriter, tell, WriterT, execWriterT, runWriterT)
import Data.Generics (everywhere, mkT)
import Data.List ({-dropWhileEnd,-} intercalate)
import Data.Map as Map (Map, lookup, insert, singleton, elems)
import Language.Haskell.TH
import Language.Haskell.TH.Context.Expand (expandPred, expandClassP)
import Language.Haskell.TH.Context.Helpers (pprint')
import Language.Haskell.TH.Desugar as DS (DsMonad)
import Language.Haskell.TH.Syntax hiding (lift)
import Language.Haskell.TH.Instances ({- Ord instances from th-orphans -})
type InstMap pred = Map pred [InstanceDec]
-- | Like 'qReifyInstances', looks up all the instances that match the
-- given class name and argument types. Unlike 'qReifyInstances',
-- only the ones that satisfy all the instance context predicates in
-- the environment are returned. If there is already an instance that
-- satisfies the predicate built from the name and types it is
-- returned. If not, this new predicate is inserted into the state
-- monad 'InstMap', associated with an empty list of predicates, and the
-- empty list is returned. Later the caller can use 'tellInstance' to
-- associate instances with the predicate.
reifyInstancesWithContext :: (DsMonad m, MonadState (InstMap Pred) m) =>
Name -> [Type] -> m [InstanceDec]
reifyInstancesWithContext className typeParameters = do
p <- expandClassP className typeParameters
mp <- get
case Map.lookup p mp of
Just x -> return x
Nothing -> do
-- Add an entry with a bogus value to limit recursion on
-- the predicate we are currently testing
modify (Map.insert p [])
-- Get all the instances of className that unify with the type parameters.
insts <- qReifyInstances className typeParameters
-- Filter out the ones that conflict with the instance context
r <- filterM (testInstance className typeParameters) insts
-- Now insert the correct value into the map and return it.
modify (Map.insert p r)
return r
-- | Test one of the instances returned by qReifyInstances against the
-- context we have computed so far. We have already added a ClassP predicate
-- for the class and argument types, we now need to unify those with the
-- type returned by the instance and generate some EqualP predicates.
testInstance :: (DsMonad m, MonadState (InstMap Pred) m) => Name -> [Type] -> InstanceDec -> m Bool
testInstance className typeParameters (InstanceD instanceContext instanceType _) = do
-- The new context consists of predicates derived by unifying the
-- type parameters with the instance type, plus the prediates in the
-- instance context field.
mapM expandPred (instancePredicates (reverse typeParameters) instanceType ++ instanceContext) >>= testContext
where
instancePredicates :: [Type] -> Type -> [Pred]
#if MIN_VERSION_template_haskell(2,10,0)
instancePredicates (x : xs) (AppT l r) = AppT (AppT EqualityT x) r : instancePredicates xs l
#else
instancePredicates (x : xs) (AppT l r) = EqualP x r : instancePredicates xs l
#endif
instancePredicates [] (ConT className') | className == className' = []
instancePredicates _ _ =
error $ (unlines ["testInstance: Failure unifying instance with arguments. This should never",
"happen because qReifyInstance returned this instance for these exact arguments:",
" typeParameters=[" ++ intercalate ", " (map show typeParameters) ++ "]",
" instanceType=" ++ show instanceType])
testInstance _ _ x = error $ "qReifyInstances returned something that doesn't appear to be an instance declaration: " ++ show x
-- | Now we have predicates representing the unification of the type
-- parameters with the instance type. Are they consistent? Find out
-- using type synonym expansion, variable substitution, elimination of
-- vacuous predicates, and unification.
testContext :: (DsMonad m, MonadState (InstMap Pred) m) => [Pred] -> m Bool
testContext context =
and <$> (mapM consistent =<< simplifyContext context)
-- | Perform type expansion on the predicates, then simplify using
-- variable substitution and eliminate vacuous equivalences.
simplifyContext :: (DsMonad m, MonadState (InstMap Pred) m) => [Pred] -> m [Pred]
simplifyContext context =
do let context' = concat $ map unify context
let context'' = foldl simplifyPredicate context' context'
if (context'' == context) then return context'' else simplifyContext context''
-- | Try to simplify the context by eliminating of one of the predicates.
-- If we succeed start again with the new context.
simplifyPredicate :: [Pred] -> Pred -> [Pred]
#if MIN_VERSION_template_haskell(2,10,0)
simplifyPredicate context (AppT (AppT EqualityT v@(VarT _)) b) = everywhere (mkT (\ x -> if x == v then b else x)) context
simplifyPredicate context (AppT (AppT EqualityT a) v@(VarT _)) = everywhere (mkT (\ x -> if x == v then a else x)) context
simplifyPredicate context p@(AppT (AppT EqualityT a) b) | a == b = filter (/= p) context
#else
simplifyPredicate context (EqualP v@(VarT _) b) = everywhere (mkT (\ x -> if x == v then b else x)) context
simplifyPredicate context (EqualP a v@(VarT _)) = everywhere (mkT (\ x -> if x == v then a else x)) context
simplifyPredicate context p@(EqualP a b) | a == b = filter (/= p) context
#endif
simplifyPredicate context _ = context
-- | Test the context (predicates) against both the instances in the Q
-- monad and the additional instances that have accumulated in the
-- State monad.
#if 0
testContextWithState :: forall m pred. (DsMonad m, MonadState (InstMap Pred) m) => [Pred] -> m Bool
testContextWithState context = do
-- Is the instance already in the Q monad?
flag <- testContext context
case flag of
True -> return True
-- Have we already generated and inserted the instance into the
-- map in the state monad? (Shouldn't we try this before calling
-- testContext?)
False -> and <$> mapM testPredicate context
where
testPredicate :: Pred -> m Bool
testPredicate predicate = maybe False (not . null) <$> (Map.lookup predicate <$> get)
#endif
-- | Unify the two arguments of an EqualP predicate, return a list of
-- simpler predicates associating types with a variables.
unify :: Pred -> [Pred]
#if MIN_VERSION_template_haskell(2,10,0)
unify (AppT (AppT EqualityT (AppT a b)) (AppT c d)) = unify (AppT (AppT EqualityT a) c) ++ unify (AppT (AppT EqualityT b) d)
unify (AppT (AppT EqualityT (ConT a)) (ConT b)) | a == b = []
unify (AppT (AppT EqualityT a@(VarT _)) b) = [AppT (AppT EqualityT a) b]
unify (AppT (AppT EqualityT a) b@(VarT _)) = [AppT (AppT EqualityT a) b]
#else
unify (EqualP (AppT a b) (AppT c d)) = unify (EqualP a c) ++ unify (EqualP b d)
unify (EqualP (ConT a) (ConT b)) | a == b = []
unify (EqualP a@(VarT _) b) = [EqualP a b]
unify (EqualP a b@(VarT _)) = [EqualP a b]
#endif
unify x = [x]
-- | Decide whether a predicate is consistent with the accumulated
-- context. Use recursive calls to qReifyInstancesWithContext when
-- we encounter a class name applied to a list of type parameters.
consistent :: (DsMonad m, MonadState (InstMap Pred) m) => Pred -> m Bool
#if MIN_VERSION_template_haskell(2,10,0)
consistent typ
| isJust (unfoldInstance typ) =
let Just (className, typeParameters) = unfoldInstance typ in
(not . null) <$> reifyInstancesWithContext className typeParameters
consistent (AppT (AppT EqualityT (AppT a b)) (AppT c d)) =
-- I'm told this is incorrect in the presence of type functions
(&&) <$> consistent (AppT (AppT EqualityT a) c) <*> consistent (AppT (AppT EqualityT b) d)
consistent (AppT (AppT EqualityT (VarT _)) _) = return True
consistent (AppT (AppT EqualityT _) (VarT _)) = return True
consistent (AppT (AppT EqualityT a) b) | a == b = return True
consistent (AppT (AppT EqualityT _) _) = return False
consistent typ = error $ "Unexpected Pred: " ++ pprint typ
#else
consistent (EqualP (AppT a b) (AppT c d)) =
-- I'm told this is incorrect in the presence of type functions
(&&) <$> consistent (EqualP a c) <*> consistent (EqualP b d)
consistent (EqualP (VarT _) _) = return True
consistent (EqualP _ (VarT _)) = return True
consistent (EqualP a b) | a == b = return True
consistent (EqualP _ _) = return False
consistent (ClassP className typeParameters) =
(not . null) <$> reifyInstancesWithContext className typeParameters -- Do we need additional context here?
#endif
-- | Declare an instance to the state and writer monads. After this,
-- the instance predicate (constructed from class namd and type
-- parameters) will be considered part of the context for subsequent
-- calls to qReifyInstancesWithContext. The instance will be returned
-- when the writer monad exists so it can be spliced into to program.
tellInstance :: (DsMonad m, MonadWriter (InstMap Pred) m, MonadState (InstMap Pred) m, Quasi m) =>
Dec -> m ()
tellInstance inst@(InstanceD _ instanceType _) =
do let Just (className, typeParameters) = unfoldInstance instanceType
#if MIN_VERSION_template_haskell(2,10,0)
p <- expandPred $ foldl AppT (ConT className) typeParameters
#else
p <- expandPred $ ClassP className typeParameters
#endif
st <- get
case Map.lookup p st of
Just (_ : _) -> return ()
_ -> do -- trace (" " ++ pprint' instanceType) (return ())
tell $ Map.singleton p [inst]
modify $ Map.insert p [inst]
tellInstance inst = error $ "tellInstance - Not an instance: " ++ pprint' inst
unfoldInstance :: Type -> Maybe (Name, [Type])
unfoldInstance (ConT name) = Just (name, [])
unfoldInstance (AppT t1 t2) = maybe Nothing (\ (name, types) -> Just (name, types ++ [t2])) (unfoldInstance t1)
unfoldInstance _ = Nothing
evalContextState :: Monad m => StateT (InstMap Pred) m r -> m r
evalContextState action = evalStateT action (mempty :: (InstMap Pred))
execContextWriter :: Monad m => WriterT (InstMap Pred) m () -> m (InstMap Pred)
execContextWriter = execWriterT
-- | Typical use: run both state and writer monads to generate a list of
-- instance declarations.
execContext :: (Monad m, Functor m) => StateT (InstMap Pred) (WriterT (InstMap Pred) m) () -> m [Dec]
execContext action = (concat . Map.elems) <$> (execWriterT $ evalContextState action)
runContext :: (Monad m, Functor m) => StateT (InstMap Pred) (WriterT (InstMap Pred) m) r -> m (r, (InstMap Pred))
runContext action = runWriterT $ evalContextState action