packages feed

haxl 0.3.1.0 → 0.4.0.0

raw patch · 16 files changed

+996/−256 lines, 16 filesdep +binarydep +ghc-primdep +test-frameworkdep ~aesondep ~basedep ~filepathnew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies added: binary, ghc-prim, test-framework, test-framework-hunit, transformers

Dependency ranges changed: aeson, base, filepath, time

API changes (from Hackage documentation)

- Haxl.Core: MemoFingerprintKey :: {-# UNPACK #-} !Word64 -> {-# UNPACK #-} !Word64 -> MemoFingerprintKey a
- Haxl.Core.DataCache: data DataCache res
- Haxl.Core.DataCache: empty :: DataCache res
- Haxl.Core.Exception: data CriticalError
- Haxl.Core.Exception: data DataSourceError
- Haxl.Core.Exception: data EmptyList
- Haxl.Core.Exception: data FetchError
- Haxl.Core.Exception: data InvalidParameter
- Haxl.Core.Exception: data JSONError
- Haxl.Core.Exception: data NotFound
- Haxl.Core.Exception: data UnexpectedType
- Haxl.Core.Exception: instance Data.Aeson.Types.Class.ToJSON Haxl.Core.Exception.HaxlException
- Haxl.Core.Memo: MemoFingerprintKey :: {-# UNPACK #-} !Word64 -> {-# UNPACK #-} !Word64 -> MemoFingerprintKey a
- Haxl.Core.Memo: instance GHC.Show.Show (Haxl.Core.Memo.MemoFingerprintKey a)
- Haxl.Core.Memo: instance GHC.Show.Show (Haxl.Core.Memo.MemoTextKey a)
- Haxl.Core.Types: instance Data.Aeson.Types.Class.ToJSON Haxl.Core.Types.DataSourceRoundStats
- Haxl.Core.Types: instance Data.Aeson.Types.Class.ToJSON Haxl.Core.Types.RoundStats
- Haxl.Core.Types: instance Data.Aeson.Types.Class.ToJSON Haxl.Core.Types.Stats
- Haxl.Prelude: data EmptyList
- Haxl.Prelude: data FetchError
- Haxl.Prelude: data InvalidParameter
- Haxl.Prelude: data NotFound
- Haxl.Prelude: data UnexpectedType
- Haxl.Prelude: instance (u1 ~ u2) => Haxl.Prelude.IfThenElse (Haxl.Core.Monad.GenHaxl u1 GHC.Types.Bool) (Haxl.Core.Monad.GenHaxl u2 a)
+ Haxl.Core: FetchCall :: String -> [String] -> RoundStats
+ Haxl.Core: ProfileData :: {-# UNPACK #-} !AllocCount -> HashSet ProfileLabel -> Map Round (HashMap Text Int) -> {-# UNPACK #-} !MemoHitCount -> ProfileData
+ Haxl.Core: [MemoFingerprintKey] :: {-# UNPACK #-} !Word64 -> {-# UNPACK #-} !Word64 -> Addr# -> Addr# -> MemoFingerprintKey a
+ Haxl.Core: [caching] :: Flags -> {-# UNPACK #-} !Int
+ Haxl.Core: [dataSourceAllocation] :: DataSourceRoundStats -> Maybe Int
+ Haxl.Core: [dataSourceFailures] :: DataSourceRoundStats -> Maybe Int
+ Haxl.Core: [fetchReq] :: RoundStats -> String
+ Haxl.Core: [fetchStack] :: RoundStats -> [String]
+ Haxl.Core: [profLabel] :: Env u -> ProfileLabel
+ Haxl.Core: [profRef] :: Env u -> {-# UNPACK #-} !(IORef Profile)
+ Haxl.Core: [profileAllocs] :: ProfileData -> {-# UNPACK #-} !AllocCount
+ Haxl.Core: [profileDeps] :: ProfileData -> HashSet ProfileLabel
+ Haxl.Core: [profileFetches] :: ProfileData -> Map Round (HashMap Text Int)
+ Haxl.Core: [profileMemoHits] :: ProfileData -> {-# UNPACK #-} !MemoHitCount
+ Haxl.Core: [roundAllocation] :: RoundStats -> Int
+ Haxl.Core: data Profile
+ Haxl.Core: data ProfileData
+ Haxl.Core: data family State f;
+ Haxl.Core: emptyProfile :: Profile
+ Haxl.Core: emptyProfileData :: ProfileData
+ Haxl.Core: ifProfiling :: (Functor m, Monad m) => Flags -> m a -> m ()
+ Haxl.Core: memoize :: GenHaxl u a -> GenHaxl u a
+ Haxl.Core: memoize1 :: (Eq a, Hashable a) => (a -> GenHaxl u b) -> GenHaxl u (a -> GenHaxl u b)
+ Haxl.Core: memoize2 :: (Eq a, Hashable a, Eq b, Hashable b) => (a -> b -> GenHaxl u c) -> GenHaxl u (a -> b -> GenHaxl u c)
+ Haxl.Core: profile :: Profile -> HashMap ProfileLabel ProfileData
+ Haxl.Core: profileCache :: Profile -> DataCache (Constant Round)
+ Haxl.Core: profileRound :: Profile -> Round
+ Haxl.Core: type ProfileLabel = Text
+ Haxl.Core: withLabel :: ProfileLabel -> GenHaxl u a -> GenHaxl u a
+ Haxl.Core: }
+ Haxl.Core.DataCache: DataCache :: (HashMap TypeRep (SubCache res)) -> DataCache res
+ Haxl.Core.DataCache: SubCache :: (req a -> String) -> (a -> String) -> !(HashMap (req a) (res a)) -> SubCache res
+ Haxl.Core.DataCache: data SubCache res
+ Haxl.Core.DataCache: emptyDataCache :: DataCache res
+ Haxl.Core.DataCache: insertWithShow :: (Hashable (req a), Typeable (req a), Eq (req a)) => (req a -> String) -> (a -> String) -> req a -> res a -> DataCache res -> DataCache res
+ Haxl.Core.DataCache: newtype DataCache res
+ Haxl.Core.Exception: LogicBug :: e -> LogicBug
+ Haxl.Core.Exception: MonadFail :: Text -> MonadFail
+ Haxl.Core.Exception: class (Exception a) => MiddleException a
+ Haxl.Core.Exception: data LogicBug
+ Haxl.Core.Exception: eName :: MiddleException a => a -> String
+ Haxl.Core.Exception: instance Data.Aeson.Types.ToJSON.ToJSON Haxl.Core.Exception.HaxlException
+ Haxl.Core.Exception: instance Data.Binary.Class.Binary Haxl.Core.Exception.CriticalError
+ Haxl.Core.Exception: instance Data.Binary.Class.Binary Haxl.Core.Exception.NonHaxlException
+ Haxl.Core.Exception: instance Data.Binary.Class.Binary Haxl.Core.Exception.NotFound
+ Haxl.Core.Exception: instance GHC.Classes.Eq Haxl.Core.Exception.MonadFail
+ Haxl.Core.Exception: instance GHC.Classes.Eq Haxl.Core.Exception.NonHaxlException
+ Haxl.Core.Exception: instance GHC.Exception.Exception Haxl.Core.Exception.LogicBug
+ Haxl.Core.Exception: instance GHC.Exception.Exception Haxl.Core.Exception.MonadFail
+ Haxl.Core.Exception: instance GHC.Exception.Exception Haxl.Core.Exception.NonHaxlException
+ Haxl.Core.Exception: instance GHC.Show.Show Haxl.Core.Exception.LogicBug
+ Haxl.Core.Exception: instance GHC.Show.Show Haxl.Core.Exception.MonadFail
+ Haxl.Core.Exception: instance GHC.Show.Show Haxl.Core.Exception.NonHaxlException
+ Haxl.Core.Exception: instance Haxl.Core.Exception.MiddleException Haxl.Core.Exception.LogicBug
+ Haxl.Core.Exception: logicBugFromException :: (Exception e) => SomeException -> Maybe e
+ Haxl.Core.Exception: logicBugToException :: (Exception e) => e -> SomeException
+ Haxl.Core.Exception: newtype CriticalError
+ Haxl.Core.Exception: newtype DataSourceError
+ Haxl.Core.Exception: newtype EmptyList
+ Haxl.Core.Exception: newtype FetchError
+ Haxl.Core.Exception: newtype InvalidParameter
+ Haxl.Core.Exception: newtype JSONError
+ Haxl.Core.Exception: newtype MonadFail
+ Haxl.Core.Exception: newtype NotFound
+ Haxl.Core.Exception: newtype UnexpectedType
+ Haxl.Core.Memo: [MemoFingerprintKey] :: {-# UNPACK #-} !Word64 -> {-# UNPACK #-} !Word64 -> Addr# -> Addr# -> MemoFingerprintKey a
+ Haxl.Core.Memo: memoize :: GenHaxl u a -> GenHaxl u a
+ Haxl.Core.Memo: memoize1 :: (Eq a, Hashable a) => (a -> GenHaxl u b) -> GenHaxl u (a -> GenHaxl u b)
+ Haxl.Core.Memo: memoize2 :: (Eq a, Hashable a, Eq b, Hashable b) => (a -> b -> GenHaxl u c) -> GenHaxl u (a -> b -> GenHaxl u c)
+ Haxl.Core.Monad: [profLabel] :: Env u -> ProfileLabel
+ Haxl.Core.Monad: [profRef] :: Env u -> {-# UNPACK #-} !(IORef Profile)
+ Haxl.Core.Monad: cacheResultWithShow :: (Eq (r a), Hashable (r a), Typeable (r a)) => ShowReq r a -> r a -> IO a -> GenHaxl u a
+ Haxl.Core.Monad: dataFetchWithShow :: (DataSource u r, Eq (r a), Hashable (r a), Typeable (r a)) => ShowReq r a -> r a -> GenHaxl u a
+ Haxl.Core.Monad: dumpCacheAsHaskellFn :: String -> String -> GenHaxl u String
+ Haxl.Core.Monad: instance Control.DeepSeq.NFData (Haxl.Core.Monad.GenHaxl u a)
+ Haxl.Core.Monad: newMemo :: GenHaxl u (MemoVar u a)
+ Haxl.Core.Monad: newMemo1 :: GenHaxl u (MemoVar1 u a b)
+ Haxl.Core.Monad: newMemo2 :: GenHaxl u (MemoVar2 u a b c)
+ Haxl.Core.Monad: newMemoWith :: GenHaxl u a -> GenHaxl u (MemoVar u a)
+ Haxl.Core.Monad: newMemoWith1 :: (a -> GenHaxl u b) -> GenHaxl u (MemoVar1 u a b)
+ Haxl.Core.Monad: newMemoWith2 :: (a -> b -> GenHaxl u c) -> GenHaxl u (MemoVar2 u a b c)
+ Haxl.Core.Monad: prepareMemo :: MemoVar u a -> GenHaxl u a -> GenHaxl u ()
+ Haxl.Core.Monad: prepareMemo1 :: MemoVar1 u a b -> (a -> GenHaxl u b) -> GenHaxl u ()
+ Haxl.Core.Monad: prepareMemo2 :: MemoVar2 u a b c -> (a -> b -> GenHaxl u c) -> GenHaxl u ()
+ Haxl.Core.Monad: runMemo :: MemoVar u a -> GenHaxl u a
+ Haxl.Core.Monad: runMemo1 :: (Eq a, Hashable a) => MemoVar1 u a b -> a -> GenHaxl u b
+ Haxl.Core.Monad: runMemo2 :: (Eq a, Hashable a, Eq b, Hashable b) => MemoVar2 u a b c -> a -> b -> GenHaxl u c
+ Haxl.Core.Monad: type ShowReq r a = (r a -> String, a -> String)
+ Haxl.Core.Monad: withFingerprintLabel :: Addr# -> Addr# -> GenHaxl u a -> GenHaxl u a
+ Haxl.Core.Monad: withLabel :: ProfileLabel -> GenHaxl u a -> GenHaxl u a
+ Haxl.Core.StateStore: data family State f;
+ Haxl.Core.StateStore: }
+ Haxl.Core.Types: DataCache :: (HashMap TypeRep (SubCache res)) -> DataCache res
+ Haxl.Core.Types: FetchCall :: String -> [String] -> RoundStats
+ Haxl.Core.Types: ProfileData :: {-# UNPACK #-} !AllocCount -> HashSet ProfileLabel -> Map Round (HashMap Text Int) -> {-# UNPACK #-} !MemoHitCount -> ProfileData
+ Haxl.Core.Types: SubCache :: (req a -> String) -> (a -> String) -> !(HashMap (req a) (res a)) -> SubCache res
+ Haxl.Core.Types: [caching] :: Flags -> {-# UNPACK #-} !Int
+ Haxl.Core.Types: [dataSourceAllocation] :: DataSourceRoundStats -> Maybe Int
+ Haxl.Core.Types: [dataSourceFailures] :: DataSourceRoundStats -> Maybe Int
+ Haxl.Core.Types: [fetchReq] :: RoundStats -> String
+ Haxl.Core.Types: [fetchStack] :: RoundStats -> [String]
+ Haxl.Core.Types: [profileAllocs] :: ProfileData -> {-# UNPACK #-} !AllocCount
+ Haxl.Core.Types: [profileDeps] :: ProfileData -> HashSet ProfileLabel
+ Haxl.Core.Types: [profileFetches] :: ProfileData -> Map Round (HashMap Text Int)
+ Haxl.Core.Types: [profileMemoHits] :: ProfileData -> {-# UNPACK #-} !MemoHitCount
+ Haxl.Core.Types: [roundAllocation] :: RoundStats -> Int
+ Haxl.Core.Types: data Profile
+ Haxl.Core.Types: data ProfileData
+ Haxl.Core.Types: data SubCache res
+ Haxl.Core.Types: emptyDataCache :: DataCache res
+ Haxl.Core.Types: emptyProfile :: Profile
+ Haxl.Core.Types: emptyProfileData :: ProfileData
+ Haxl.Core.Types: ifProfiling :: (Functor m, Monad m) => Flags -> m a -> m ()
+ Haxl.Core.Types: instance Data.Aeson.Types.ToJSON.ToJSON Haxl.Core.Types.DataSourceRoundStats
+ Haxl.Core.Types: instance Data.Aeson.Types.ToJSON.ToJSON Haxl.Core.Types.RoundStats
+ Haxl.Core.Types: instance Data.Aeson.Types.ToJSON.ToJSON Haxl.Core.Types.Stats
+ Haxl.Core.Types: instance GHC.Show.Show Haxl.Core.Types.DataSourceRoundStats
+ Haxl.Core.Types: instance GHC.Show.Show Haxl.Core.Types.ProfileData
+ Haxl.Core.Types: instance GHC.Show.Show Haxl.Core.Types.RoundStats
+ Haxl.Core.Types: instance GHC.Show.Show Haxl.Core.Types.Stats
+ Haxl.Core.Types: newtype DataCache res
+ Haxl.Core.Types: profile :: Profile -> HashMap ProfileLabel ProfileData
+ Haxl.Core.Types: profileCache :: Profile -> DataCache (Constant Round)
+ Haxl.Core.Types: profileRound :: Profile -> Round
+ Haxl.Core.Types: type AllocCount = Int64
+ Haxl.Core.Types: type MemoHitCount = Int64
+ Haxl.Core.Types: type ProfileLabel = Text
+ Haxl.Core.Types: type Round = Int
+ Haxl.Prelude: infix 4 ./=
+ Haxl.Prelude: infixr 2 .||
+ Haxl.Prelude: infixr 3 .&&
+ Haxl.Prelude: instance u1 ~ u2 => Haxl.Prelude.IfThenElse (Haxl.Core.Monad.GenHaxl u1 GHC.Types.Bool) (Haxl.Core.Monad.GenHaxl u2 a)
+ Haxl.Prelude: memoize :: GenHaxl u a -> GenHaxl u a
+ Haxl.Prelude: memoize1 :: (Eq a, Hashable a) => (a -> GenHaxl u b) -> GenHaxl u (a -> GenHaxl u b)
+ Haxl.Prelude: memoize2 :: (Eq a, Hashable a, Eq b, Hashable b) => (a -> b -> GenHaxl u c) -> GenHaxl u (a -> b -> GenHaxl u c)
+ Haxl.Prelude: newtype EmptyList
+ Haxl.Prelude: newtype FetchError
+ Haxl.Prelude: newtype InvalidParameter
+ Haxl.Prelude: newtype NotFound
+ Haxl.Prelude: newtype UnexpectedType
- Haxl.Core: DataSourceRoundStats :: Int -> Maybe Microseconds -> DataSourceRoundStats
+ Haxl.Core: DataSourceRoundStats :: Int -> Maybe Microseconds -> Maybe Int -> Maybe Int -> DataSourceRoundStats
- Haxl.Core: Env :: IORef (DataCache ResultVar) -> IORef (DataCache (MemoVar u)) -> Flags -> u -> IORef Stats -> StateStore -> Env u
+ Haxl.Core: Env :: {-# UNPACK #-} !(IORef (DataCache ResultVar)) -> {-# UNPACK #-} !(IORef (DataCache (MemoVar u))) -> !Flags -> u -> {-# UNPACK #-} !(IORef Stats) -> ProfileLabel -> {-# UNPACK #-} !(IORef Profile) -> StateStore -> Env u
- Haxl.Core: Flags :: Int -> Int -> Flags
+ Haxl.Core: Flags :: {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> Flags
- Haxl.Core: RoundStats :: Microseconds -> HashMap Text DataSourceRoundStats -> RoundStats
+ Haxl.Core: RoundStats :: Microseconds -> Int -> HashMap Text DataSourceRoundStats -> RoundStats
- Haxl.Core: [cacheRef] :: Env u -> IORef (DataCache ResultVar)
+ Haxl.Core: [cacheRef] :: Env u -> {-# UNPACK #-} !(IORef (DataCache ResultVar))
- Haxl.Core: [flags] :: Env u -> Flags
+ Haxl.Core: [flags] :: Env u -> !Flags
- Haxl.Core: [memoRef] :: Env u -> IORef (DataCache (MemoVar u))
+ Haxl.Core: [memoRef] :: Env u -> {-# UNPACK #-} !(IORef (DataCache (MemoVar u)))
- Haxl.Core: [report] :: Flags -> Int
+ Haxl.Core: [report] :: Flags -> {-# UNPACK #-} !Int
- Haxl.Core: [statsRef] :: Env u -> IORef Stats
+ Haxl.Core: [statsRef] :: Env u -> {-# UNPACK #-} !(IORef Stats)
- Haxl.Core: [trace] :: Flags -> Int
+ Haxl.Core: [trace] :: Flags -> {-# UNPACK #-} !Int
- Haxl.Core: cacheRequest :: (Request req a) => req a -> Either SomeException a -> GenHaxl u ()
+ Haxl.Core: cacheRequest :: Request req a => req a -> Either SomeException a -> GenHaxl u ()
- Haxl.Core: cacheResult :: (Request r a) => r a -> IO a -> GenHaxl u a
+ Haxl.Core: cacheResult :: Request r a => r a -> IO a -> GenHaxl u a
- Haxl.Core: cachedComputation :: (Eq (req a), Hashable (req a), Typeable (req a)) => req a -> GenHaxl u a -> GenHaxl u a
+ Haxl.Core: cachedComputation :: forall req u a. (Eq (req a), Hashable (req a), Typeable (req a)) => req a -> GenHaxl u a -> GenHaxl u a
- Haxl.Core: class Typeable f => StateKey (f :: * -> *) where data family State f
+ Haxl.Core: class Typeable f => StateKey (f :: * -> *) where data State f where {
- Haxl.Core: memoFingerprint :: (Show a, Typeable a) => MemoFingerprintKey a -> GenHaxl u a -> GenHaxl u a
+ Haxl.Core: memoFingerprint :: Typeable a => MemoFingerprintKey a -> GenHaxl u a -> GenHaxl u a
- Haxl.Core: stateGet :: StateKey r => StateStore -> Maybe (State r)
+ Haxl.Core: stateGet :: forall r. StateKey r => StateStore -> Maybe (State r)
- Haxl.Core: stateSet :: StateKey f => State f -> StateStore -> StateStore
+ Haxl.Core: stateSet :: forall f. StateKey f => State f -> StateStore -> StateStore
- Haxl.Core: uncachedRequest :: (DataSource u r, Request r a) => r a -> GenHaxl u a
+ Haxl.Core: uncachedRequest :: (DataSource u r, Show (r a)) => r a -> GenHaxl u a
- Haxl.Core.Exception: HaxlException :: e -> HaxlException
+ Haxl.Core.Exception: HaxlException :: (Maybe Stack) -> e -> HaxlException
- Haxl.Core.Memo: memoFingerprint :: (Show a, Typeable a) => MemoFingerprintKey a -> GenHaxl u a -> GenHaxl u a
+ Haxl.Core.Memo: memoFingerprint :: Typeable a => MemoFingerprintKey a -> GenHaxl u a -> GenHaxl u a
- Haxl.Core.Monad: Env :: IORef (DataCache ResultVar) -> IORef (DataCache (MemoVar u)) -> Flags -> u -> IORef Stats -> StateStore -> Env u
+ Haxl.Core.Monad: Env :: {-# UNPACK #-} !(IORef (DataCache ResultVar)) -> {-# UNPACK #-} !(IORef (DataCache (MemoVar u))) -> !Flags -> u -> {-# UNPACK #-} !(IORef Stats) -> ProfileLabel -> {-# UNPACK #-} !(IORef Profile) -> StateStore -> Env u
- Haxl.Core.Monad: [cacheRef] :: Env u -> IORef (DataCache ResultVar)
+ Haxl.Core.Monad: [cacheRef] :: Env u -> {-# UNPACK #-} !(IORef (DataCache ResultVar))
- Haxl.Core.Monad: [flags] :: Env u -> Flags
+ Haxl.Core.Monad: [flags] :: Env u -> !Flags
- Haxl.Core.Monad: [memoRef] :: Env u -> IORef (DataCache (MemoVar u))
+ Haxl.Core.Monad: [memoRef] :: Env u -> {-# UNPACK #-} !(IORef (DataCache (MemoVar u)))
- Haxl.Core.Monad: [statsRef] :: Env u -> IORef Stats
+ Haxl.Core.Monad: [statsRef] :: Env u -> {-# UNPACK #-} !(IORef Stats)
- Haxl.Core.Monad: cacheRequest :: (Request req a) => req a -> Either SomeException a -> GenHaxl u ()
+ Haxl.Core.Monad: cacheRequest :: Request req a => req a -> Either SomeException a -> GenHaxl u ()
- Haxl.Core.Monad: cacheResult :: (Request r a) => r a -> IO a -> GenHaxl u a
+ Haxl.Core.Monad: cacheResult :: Request r a => r a -> IO a -> GenHaxl u a
- Haxl.Core.Monad: cachedComputation :: (Eq (req a), Hashable (req a), Typeable (req a)) => req a -> GenHaxl u a -> GenHaxl u a
+ Haxl.Core.Monad: cachedComputation :: forall req u a. (Eq (req a), Hashable (req a), Typeable (req a)) => req a -> GenHaxl u a -> GenHaxl u a
- Haxl.Core.Monad: uncachedRequest :: (DataSource u r, Request r a) => r a -> GenHaxl u a
+ Haxl.Core.Monad: uncachedRequest :: (DataSource u r, Show (r a)) => r a -> GenHaxl u a
- Haxl.Core.RequestStore: addRequest :: (DataSource u r) => BlockedFetch r -> RequestStore u -> RequestStore u
+ Haxl.Core.RequestStore: addRequest :: forall u r. (DataSource u r) => BlockedFetch r -> RequestStore u -> RequestStore u
- Haxl.Core.StateStore: class Typeable f => StateKey (f :: * -> *) where data family State f
+ Haxl.Core.StateStore: class Typeable f => StateKey (f :: * -> *) where data State f where {
- Haxl.Core.StateStore: stateGet :: StateKey r => StateStore -> Maybe (State r)
+ Haxl.Core.StateStore: stateGet :: forall r. StateKey r => StateStore -> Maybe (State r)
- Haxl.Core.StateStore: stateSet :: StateKey f => State f -> StateStore -> StateStore
+ Haxl.Core.StateStore: stateSet :: forall f. StateKey f => State f -> StateStore -> StateStore
- Haxl.Core.Types: DataSourceRoundStats :: Int -> Maybe Microseconds -> DataSourceRoundStats
+ Haxl.Core.Types: DataSourceRoundStats :: Int -> Maybe Microseconds -> Maybe Int -> Maybe Int -> DataSourceRoundStats
- Haxl.Core.Types: Flags :: Int -> Int -> Flags
+ Haxl.Core.Types: Flags :: {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> Flags
- Haxl.Core.Types: RoundStats :: Microseconds -> HashMap Text DataSourceRoundStats -> RoundStats
+ Haxl.Core.Types: RoundStats :: Microseconds -> Int -> HashMap Text DataSourceRoundStats -> RoundStats
- Haxl.Core.Types: [report] :: Flags -> Int
+ Haxl.Core.Types: [report] :: Flags -> {-# UNPACK #-} !Int
- Haxl.Core.Types: [trace] :: Flags -> Int
+ Haxl.Core.Types: [trace] :: Flags -> {-# UNPACK #-} !Int
- Haxl.Prelude: (*>) :: Applicative f => f a -> f b -> f b
+ Haxl.Prelude: (*>) :: f a -> f b -> f b
- Haxl.Prelude: (<*) :: Applicative f => f a -> f b -> f a
+ Haxl.Prelude: (<*) :: f a -> f b -> f a
- Haxl.Prelude: (<*>) :: Applicative f => f (a -> b) -> f a -> f b
+ Haxl.Prelude: (<*>) :: f (a -> b) -> f a -> f b
- Haxl.Prelude: HaxlException :: e -> HaxlException
+ Haxl.Prelude: HaxlException :: (Maybe Stack) -> e -> HaxlException
- Haxl.Prelude: fromString :: IsString a => String -> a
+ Haxl.Prelude: fromString :: String -> a
- Haxl.Prelude: mappend :: Monoid a => a -> a -> a
+ Haxl.Prelude: mappend :: a -> a -> a
- Haxl.Prelude: mconcat :: Monoid a => [a] -> a
+ Haxl.Prelude: mconcat :: [a] -> a
- Haxl.Prelude: mempty :: Monoid a => a
+ Haxl.Prelude: mempty :: a
- Haxl.Prelude: pure :: Applicative f => a -> f a
+ Haxl.Prelude: pure :: a -> f a

Files

Haxl/Core.hs view
@@ -15,7 +15,7 @@   -- ** Env   Env(..), Caches, caches,   -- *** Operations in the monad-  env, withEnv,+  env, withEnv, withLabel,   -- *** Building the Env   initEnvWithData, initEnv, emptyEnv,   -- *** Building the StateStore@@ -30,7 +30,8 @@   dumpCacheAsHaskell,    -- ** Memoization-  memo, memoFingerprint, MemoFingerprintKey(..),+  memo, memoize, memoize1, memoize2,+  memoFingerprint, MemoFingerprintKey(..),    -- ** Statistics   Stats(..),@@ -43,12 +44,21 @@   ppStats,   ppRoundStats,   ppDataSourceRoundStats,+  Profile,+  emptyProfile,+  profile,+  profileRound,+  profileCache,+  ProfileLabel,+  ProfileData(..),+  emptyProfileData,    -- ** Tracing flags   Flags(..),   defaultFlags,   ifTrace,   ifReport,+  ifProfiling,    -- * Building data sources   DataSource(..),
Haxl/Core/DataCache.hs view
@@ -14,15 +14,16 @@ -- provided for access to Haxl internals only; most users should not -- need to import it. module Haxl.Core.DataCache-  ( DataCache-  , empty+  ( DataCache(..)+  , SubCache(..)+  , emptyDataCache   , insert   , insertNotShowable+  , insertWithShow   , lookup   , showCache   ) where -import Data.HashMap.Strict (HashMap) import Data.Hashable import Prelude hiding (lookup) import Unsafe.Coerce@@ -30,35 +31,12 @@ import Data.Typeable.Internal import Data.Maybe #if __GLASGOW_HASKELL__ < 710-import Control.Applicative hiding (empty)+import Control.Applicative ((<$>)) #endif import Control.Exception  import Haxl.Core.Types --- | The 'DataCache' maps things of type @f a@ to @'ResultVar' a@, for--- any @f@ and @a@ provided @f a@ is an instance of 'Typeable'. In--- practice @f a@ will be a request type parameterised by its result.------ See the definition of 'ResultVar' for more details.--newtype DataCache res = DataCache (HashMap TypeRep (SubCache res))---- | The implementation is a two-level map: the outer level maps the--- types of requests to 'SubCache', which maps actual requests to their--- results.  So each 'SubCache' contains requests of the same type.--- This works well because we only have to store the dictionaries for--- 'Hashable' and 'Eq' once per request type.-data SubCache res =-  forall req a . (Hashable (req a), Eq (req a)) =>-       SubCache (req a -> String) (a -> String) ! (HashMap (req a) (res a))-       -- NB. the inner HashMap is strict, to avoid building up-       -- a chain of thunks during repeated insertions.---- | A new, empty 'DataCache'.-empty :: DataCache res-empty = DataCache HashMap.empty- -- | Inserts a request-result pair into the 'DataCache'. insert   :: (Hashable (req a), Typeable (req a), Eq (req a), Show (req a), Show a)@@ -69,10 +47,27 @@   -> DataCache res   -> DataCache res -insert req result (DataCache m) =+insert = insertWithShow show show++-- | Inserts a request-result pair into the 'DataCache', with the given+-- functions used to show the request and result.+insertWithShow+  :: (Hashable (req a), Typeable (req a), Eq (req a))+  => (req a -> String)+  -- ^ Show function for request+  -> (a -> String)+  -- ^ Show function for result+  -> req a+  -- ^ Request+  -> res a+  -- ^ Result+  -> DataCache res+  -> DataCache res++insertWithShow showRequest showResult req result (DataCache m) =   DataCache $     HashMap.insertWith fn (typeOf req)-       (SubCache show show (HashMap.singleton req result)) m+       (SubCache showRequest showResult (HashMap.singleton req result)) m   where     fn (SubCache _ _ new) (SubCache showReq showRes old) =       SubCache showReq showRes (unsafeCoerce new `HashMap.union` old)@@ -89,13 +84,7 @@   -> DataCache res   -> DataCache res -insertNotShowable req result (DataCache m) =-  DataCache $-    HashMap.insertWith fn (typeOf req)-       (SubCache notShowable notShowable (HashMap.singleton req result)) m-  where-    fn (SubCache _ _ new) (SubCache showReq showRes old) =-      SubCache showReq showRes (unsafeCoerce new `HashMap.union` old)+insertNotShowable = insertWithShow notShowable notShowable  notShowable :: a notShowable = error "insertNotShowable"@@ -115,9 +104,9 @@            unsafeCoerce (HashMap.lookup (unsafeCoerce req) sc)  -- | Dumps the contents of the cache, with requests and responses--- converted to 'String's using 'show'.  The entries are grouped by--- 'TypeRep'.  Note that this will fail if 'insertNotShowable' has--- been used to insert any entries.+-- converted to 'String's using the supplied show functions.  The+-- entries are grouped by 'TypeRep'.  Note that this will fail if+-- 'insertNotShowable' has been used to insert any entries. -- showCache   :: DataCache ResultVar
Haxl/Core/Exception.hs view
@@ -5,6 +5,7 @@ -- found in the LICENSE file. An additional grant of patent rights can -- be found in the PATENTS file. +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE DeriveDataTypeable #-}@@ -40,6 +41,10 @@   logicErrorToException,   logicErrorFromException, +  LogicBug(..),+  logicBugToException,+  logicBugFromException,+   TransientError(..),   transientErrorToException,   transientErrorFromException,@@ -54,21 +59,25 @@   EmptyList(..),   JSONError(..),   InvalidParameter(..),+  MonadFail(..),    -- ** Transient exceptions   FetchError(..),    -- * Exception utilities   asHaxlException,+  MiddleException(..),    ) where  import Control.Exception import Data.Aeson+import Data.Binary (Binary) import Data.Typeable import Data.Text (Text)  import Haxl.Core.Util+import GHC.Stack  -- | We have a 3-tiered hierarchy of exceptions, with 'HaxlException' at -- the top, and all Haxl exceptions as children of this. Users should@@ -78,8 +87,11 @@ -- --   ['InternalError']  Something is wrong with Haxl core. -----   ['LogicError']     Something is wrong with Haxl client code.+--   ['LogicBug']       Something is wrong with Haxl client code. --+--   ['LogicError']     Things that really should be return values, e.g.+--                      NotFound.+-- --   ['TransientError'] Something is temporarily failing (usually in a fetch). -- -- These are not meant to be thrown (but likely be caught). Thrown@@ -88,30 +100,42 @@ -- (generic transient failure) or 'CriticalError' (internal failure). -- data HaxlException-  = forall e. (Exception e, MiddleException e) => HaxlException e+  = forall e. (Exception e, MiddleException e)+    => HaxlException+         (Maybe Stack)  -- filled in with the call stack when thrown,+                        -- if PROFILING is on+         e   deriving (Typeable) +type Stack = [String]+  -- hopefully this will get more informative in the future+ instance Show HaxlException where-  show (HaxlException e) = show e+  show (HaxlException (Just stk@(_:_)) e) = show e ++ '\n' : renderStack stk+  show (HaxlException _ e) = show e  instance Exception HaxlException  -- | These need to be serializable to JSON to cross FFI boundaries. instance ToJSON HaxlException where-  toJSON (HaxlException e) = object-    [ "type" .= show (typeOf e)-    , "name" .= eName e-    , "txt"  .= show e-    ]+  toJSON (HaxlException stk e) = object fields+    where+      fields | Just s@(_:_) <- stk = ("stack" .= reverse s) : rest+             | otherwise = rest+      rest =+        [ "type" .= show (typeOf e)+        , "name" .= eName e+        , "txt"  .= show e+        ]  haxlExceptionToException   :: (Exception e, MiddleException e) => e -> SomeException-haxlExceptionToException = toException . HaxlException+haxlExceptionToException = toException . HaxlException Nothing  haxlExceptionFromException   :: (Exception e, MiddleException e) => SomeException -> Maybe e haxlExceptionFromException x = do-  HaxlException a <- fromException x+  HaxlException _ a <- fromException x   cast a  class (Exception a) => MiddleException a where@@ -183,29 +207,61 @@   LogicError a <- fromException x   cast a +data LogicBug = forall e . (Exception e) => LogicBug e+  deriving (Typeable)++deriving instance Show LogicBug++instance Exception LogicBug where+ toException   = haxlExceptionToException+ fromException = haxlExceptionFromException++instance MiddleException LogicBug where+  eName (LogicBug e) = show $ typeOf e++logicBugToException :: (Exception e) => e -> SomeException+logicBugToException = toException . LogicBug++logicBugFromException+  :: (Exception e) => SomeException -> Maybe e+logicBugFromException x = do+  LogicBug a <- fromException x+  cast a+ ------------------------------------------------------------------------ -- Leaf exceptions. You should throw these. Or make your own. ------------------------------------------------------------------------  -- | Generic \"critical\" exception. Something internal is -- borked. Panic.-data CriticalError = CriticalError Text-  deriving (Typeable, Eq, Show)+newtype CriticalError = CriticalError Text+  deriving (Typeable, Binary, Eq, Show)  instance Exception CriticalError where   toException   = internalErrorToException   fromException = internalErrorFromException +-- | Exceptions that are converted to HaxlException by+-- asHaxlException.  Typically these will be pure exceptions,+-- e.g., the 'error' function in pure code, or a pattern-match+-- failure.+newtype NonHaxlException = NonHaxlException Text+  deriving (Typeable, Binary, Eq, Show)++instance Exception NonHaxlException where+  toException   = internalErrorToException+  fromException = internalErrorFromException+ -- | Generic \"something was not found\" exception.-data NotFound = NotFound Text-  deriving (Typeable, Eq, Show)+newtype NotFound = NotFound Text+  deriving (Typeable, Binary, Eq, Show)  instance Exception NotFound where   toException = logicErrorToException   fromException = logicErrorFromException  -- | Generic \"something had the wrong type\" exception.-data UnexpectedType = UnexpectedType Text+newtype UnexpectedType = UnexpectedType Text   deriving (Typeable, Eq, Show)  instance Exception UnexpectedType where@@ -213,15 +269,16 @@   fromException = logicErrorFromException  -- | Generic \"input list was empty\" exception.-data EmptyList = EmptyList Text+newtype EmptyList = EmptyList Text   deriving (Typeable, Eq, Show)  instance Exception EmptyList where   toException = logicErrorToException   fromException = logicErrorFromException+  -- TODO: should be a child of LogicBug  -- | Generic \"Incorrect assumptions about JSON data\" exception.-data JSONError = JSONError Text+newtype JSONError = JSONError Text   deriving (Typeable, Eq, Show)  instance Exception JSONError where@@ -229,15 +286,24 @@   fromException = logicErrorFromException  -- | Generic \"passing some invalid parameter\" exception.-data InvalidParameter = InvalidParameter Text+newtype InvalidParameter = InvalidParameter Text   deriving (Typeable, Eq, Show)  instance Exception InvalidParameter where   toException = logicErrorToException   fromException = logicErrorFromException+  -- TODO: should be a child of LogicBug +-- | Generic \"fail was called\" exception.+newtype MonadFail = MonadFail Text+  deriving (Typeable, Eq, Show)++instance Exception MonadFail where+  toException = logicErrorToException+  fromException = logicErrorFromException+ -- | Generic transient fetching exceptions.-data FetchError = FetchError Text+newtype FetchError = FetchError Text   deriving (Typeable, Eq, Show)  instance Exception FetchError where@@ -245,7 +311,7 @@   fromException = transientErrorFromException  -- | A data source did something wrong-data DataSourceError = DataSourceError Text+newtype DataSourceError = DataSourceError Text   deriving (Typeable, Eq, Show)  instance Exception DataSourceError where@@ -253,10 +319,10 @@   fromException = internalErrorFromException  -- | Converts all exceptions that are not derived from 'HaxlException'--- into 'CriticalError', using 'show'.+-- into 'NonHaxlException', using 'show'. asHaxlException :: SomeException -> HaxlException asHaxlException e   | Just haxl_exception <- fromException e = -- it's a HaxlException      haxl_exception   | otherwise =-     HaxlException (InternalError (CriticalError (textShow e)))+     HaxlException Nothing (InternalError (NonHaxlException (textShow e)))
Haxl/Core/Memo.hs view
@@ -5,20 +5,34 @@ -- found in the LICENSE file. An additional grant of patent rights can -- be found in the PATENTS file. -{-# LANGUAGE DeriveDataTypeable, GADTs, OverloadedStrings,-    StandaloneDeriving #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StandaloneDeriving #-}  -- | Most users should import "Haxl.Core" instead of importing this -- module directly.-module Haxl.Core.Memo (memo, memoFingerprint, MemoFingerprintKey(..)) where+module Haxl.Core.Memo (+  memo,+  memoFingerprint,+  MemoFingerprintKey(..),+  memoize, memoize1, memoize2+) where +#if __GLASGOW_HASKELL__ < 710+import Control.Applicative+#endif import Data.Text (Text) import Data.Typeable import Data.Hashable import Data.Word -import Haxl.Core.Monad (GenHaxl, cachedComputation)+import GHC.Prim (Addr#) +import Haxl.Core.Monad+ -- ----------------------------------------------------------------------------- -- A key type that can be used for memoizing computations by a Text key @@ -55,39 +69,81 @@   deriving Typeable  deriving instance Eq (MemoTextKey a)-deriving instance Show (MemoTextKey a)  instance Hashable (MemoTextKey a) where   hashWithSalt s (MemoText t) = hashWithSalt s t  memoText :: (Typeable a) => Text -> GenHaxl u a -> GenHaxl u a-memoText key = cachedComputation (MemoText key)+memoText key = withLabel key . cachedComputation (MemoText key)  -- | A memo key derived from a 128-bit MD5 hash.  Do not use this directly, -- it is for use by automatically-generated memoization.- data MemoFingerprintKey a where   MemoFingerprintKey     :: {-# UNPACK #-} !Word64-    -> {-# UNPACK #-} !Word64  -> MemoFingerprintKey a+    -> {-# UNPACK #-} !Word64+    -> Addr# -> Addr#+    -> MemoFingerprintKey a   deriving Typeable  deriving instance Eq (MemoFingerprintKey a)-deriving instance Show (MemoFingerprintKey a)  instance Hashable (MemoFingerprintKey a) where-  hashWithSalt s (MemoFingerprintKey x _) =+  hashWithSalt s (MemoFingerprintKey x _ _ _) =     hashWithSalt s (fromIntegral x :: Int)  -- This is optimised for cheap call sites: when we have a call -----   memoFingerprint (MemoFingerprintKey 1234 5678) e+--   memoFingerprint (MemoFingerprintKey 1234 5678 "module"# "name"#) e -- -- then the MemoFingerprintKey constructor will be statically--- allocated (with two 64-bit fields), and shared by all calls to--- memo.  So the memo call will not allocate, unlike memoText.+-- allocated (with two 64-bit fields and pointers to cstrings for the names),+-- and shared by all calls to memo. So the memo call will not allocate,+-- unlike memoText. -- {-# NOINLINE memoFingerprint #-} memoFingerprint-   :: (Show a, Typeable a) => MemoFingerprintKey a -> GenHaxl u a -> GenHaxl u a-memoFingerprint key = cachedComputation key+  :: Typeable a => MemoFingerprintKey a -> GenHaxl u a -> GenHaxl u a+memoFingerprint key@(MemoFingerprintKey _ _ mnPtr nPtr) =+  withFingerprintLabel mnPtr nPtr . cachedComputation key++-- * Generic memoization machinery.++-- | Transform a Haxl computation into a memoized version of itself.+--+-- Given a Haxl computation, @memoize@ creates a version which stores its result+-- in a @MemoVar@ (which @memoize@ creates), and returns the stored result on+-- subsequent invocations. This permits the creation of local memos, whose+-- lifetimes are scoped to the current function, rather than the entire request.+memoize :: GenHaxl u a -> GenHaxl u a+memoize a = newMemoWith a >>= runMemo++-- | Transform a 1-argument function returning a Haxl computation into a+-- memoized version of itself.+--+-- Given a function @f@ of type @a -> GenHaxl u b@, @memoize1@ creates a version+-- which memoizes the results of @f@ in a table keyed by its argument, and+-- returns stored results on subsequent invocations with the same argument.+--+-- e.g.:+--+-- allFriends :: [Int] -> GenHaxl u [Int]+-- allFriends ids = do+--   memoizedFriendsOf <- memoize1 friendsOf+--   concat <$> mapM memoizeFriendsOf ids+--+-- The above implementation will not invoke the underlying @friendsOf@+-- repeatedly for duplicate values in @ids@.+memoize1 :: (Eq a, Hashable a)+         => (a -> GenHaxl u b)+         -> GenHaxl u (a -> GenHaxl u b)+memoize1 f = runMemo1 <$> newMemoWith1 f++-- | Transform a 2-argument function returning a Haxl computation, into a+-- memoized version of itself.+--+-- The 2-ary version of @memoize1@, see its documentation for details.+memoize2 :: (Eq a, Hashable a, Eq b, Hashable b)+         => (a -> b -> GenHaxl u c)+         -> GenHaxl u (a -> b -> GenHaxl u c)+memoize2 f = runMemo2 <$> newMemoWith2 f
Haxl/Core/Monad.hs view
@@ -5,23 +5,28 @@ -- found in the LICENSE file. An additional grant of patent rights can -- be found in the PATENTS file. -{-# LANGUAGE CPP #-} {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MagicHash #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternGuards #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}  -- | The implementation of the 'Haxl' monad.  Most users should -- import "Haxl.Core" instead of importing this module directly. module Haxl.Core.Monad (     -- * The monad     GenHaxl (..), runHaxl,-    env, withEnv,+    env, withEnv, withLabel, withFingerprintLabel,      -- * Env     Env(..), Caches, caches, initEnvWithData, initEnv, emptyEnv,@@ -30,10 +35,16 @@     throw, catch, catchIf, try, tryToHaxlException,      -- * Data fetching and caching-    dataFetch, uncachedRequest,-    cacheRequest, cacheResult, cachedComputation,-    dumpCacheAsHaskell,+    ShowReq, dataFetch, dataFetchWithShow, uncachedRequest, cacheRequest,+    cacheResult, cacheResultWithShow, cachedComputation,+    dumpCacheAsHaskell, dumpCacheAsHaskellFn, +    -- * Memoization Machinery+    newMemo, newMemoWith, prepareMemo, runMemo,++    newMemo1, newMemoWith1, prepareMemo1, runMemo1,+    newMemo2, newMemoWith2, prepareMemo2, runMemo2,+     -- * Unsafe operations     unsafeLiftIO, unsafeToHaxlException,   ) where@@ -54,6 +65,7 @@ #endif #if __GLASGOW_HASKELL__ >= 710 import Control.Exception (AllocationLimitExceeded(..))+import GHC.Conc (getAllocationCounter, setAllocationCounter) #endif import Control.Monad import qualified Control.Exception as Exception@@ -61,35 +73,59 @@ import Control.Applicative hiding (Const) #endif import Control.DeepSeq-import GHC.Exts (IsString(..))+import GHC.Exts (IsString(..), Addr#) #if __GLASGOW_HASKELL__ < 706 import Prelude hiding (catch) #endif+import Data.Functor.Constant import Data.Hashable+import qualified Data.HashMap.Strict as HashMap+import qualified Data.HashSet as HashSet import Data.IORef import Data.List+import qualified Data.Map as Map import Data.Monoid import Data.Time import Data.Typeable-import qualified Data.HashMap.Strict as HashMap import Text.Printf import Text.PrettyPrint hiding ((<>)) import Control.Arrow (left)+ #ifdef EVENTLOG import Control.Exception (bracket_) import Debug.Trace (traceEventIO) #endif +#ifdef PROFILING+import GHC.Stack+#endif++#if __GLASGOW_HASKELL__ < 710+import Data.Int (Int64)++getAllocationCounter :: IO Int64+getAllocationCounter = return 0++setAllocationCounter :: Int64 -> IO ()+setAllocationCounter _ = return ()+#endif+ -- ----------------------------------------------------------------------------- -- The environment  -- | The data we carry around in the Haxl monad. data Env u = Env-  { cacheRef     :: IORef (DataCache ResultVar) -- cached data fetches-  , memoRef      :: IORef (DataCache (MemoVar u))   -- memoized computations-  , flags        :: Flags+  { cacheRef     :: {-# UNPACK #-} !(IORef (DataCache ResultVar))+                     -- cached data fetches+  , memoRef      :: {-# UNPACK #-} !(IORef (DataCache (MemoVar u)))+                     -- memoized computations+  , flags        :: !Flags+                     -- conservatively not unpacking, because this is passed+                     -- to 'fetch' and would need to be rebuilt.   , userEnv      :: u-  , statsRef     :: IORef Stats+  , statsRef     :: {-# UNPACK #-} !(IORef Stats)+  , profLabel    :: ProfileLabel+  , profRef      :: {-# UNPACK #-} !(IORef Profile)   , states       :: StateStore   -- ^ Data sources and other components can store their state in   -- here. Items in this store must be instances of 'StateKey'.@@ -105,6 +141,7 @@ initEnvWithData :: StateStore -> u -> Caches u -> IO (Env u) initEnvWithData states e (cref, mref) = do   sref <- newIORef emptyStats+  pref <- newIORef emptyProfile   return Env     { cacheRef = cref     , memoRef = mref@@ -112,13 +149,15 @@     , userEnv = e     , states = states     , statsRef = sref+    , profLabel = "MAIN"+    , profRef = pref     }  -- | Initializes an environment with 'StateStore' and an input map. initEnv :: StateStore -> u -> IO (Env u) initEnv states e = do-  cref <- newIORef DataCache.empty-  mref <- newIORef DataCache.empty+  cref <- newIORef emptyDataCache+  mref <- newIORef emptyDataCache   initEnvWithData states e (cref,mref)  -- | A new, empty environment.@@ -144,6 +183,7 @@ -- newtype GenHaxl u a = GenHaxl   { unHaxl :: Env u -> IORef (RequestStore u) -> IO (Result u a) }+  deriving NFData  -- | The result of a computation is either 'Done' with a value, 'Throw' -- with an exception, or 'Blocked' on the result of a data fetch with@@ -204,6 +244,8 @@       Done a       -> unHaxl (k a) env ref       Throw e      -> return (Throw e)       Blocked cont -> return (Blocked (cont :>>= k))+  fail msg = GenHaxl $ \_env _ref ->+    return $ Throw $ toException $ MonadFail $ Text.pack msg    -- We really want the Applicative version of >>   (>>) = (*>)@@ -242,7 +284,7 @@   let go !n env c = do         traceEventIO "START computation"         ref <- newIORef noRequests-        e <- toHaxl c env ref+        e <- (unHaxl $ toHaxl c) env ref         traceEventIO "STOP computation"         case e of           Done a       -> return a@@ -253,6 +295,8 @@             traceEventIO "START performFetches"             n' <- performFetches n env bs             traceEventIO "STOP performFetches"+            when (caching (flags env) == 0) $+              writeIORef (cacheRef env) DataCache.empty             go n' env cont   traceEventIO "START runHaxl"   r <- go 0 env (Cont h)@@ -269,6 +313,8 @@       bs <- readIORef ref       writeIORef ref noRequests -- Note [RoundId]       void (performFetches 0 env bs)+      when (caching (flags env) == 0) $+        writeIORef (cacheRef env) emptyDataCache       runHaxl env (toHaxl cont) #endif @@ -286,6 +332,72 @@     Throw e -> return (Throw e)     Blocked k -> return (Blocked (Cont (withEnv newEnv (toHaxl k)))) +-- | Label a computation so profiling data is attributed to the label.+withLabel :: ProfileLabel -> GenHaxl u a -> GenHaxl u a+withLabel l (GenHaxl m) = GenHaxl $ \env ref ->+  if report (flags env) < 4+     then m env ref+     else collectProfileData l m env ref++-- | Label a computation so profiling data is attributed to the label.+-- Intended only for internal use by 'memoFingerprint'.+withFingerprintLabel :: Addr# -> Addr# -> GenHaxl u a -> GenHaxl u a+withFingerprintLabel mnPtr nPtr (GenHaxl m) = GenHaxl $ \env ref ->+  if report (flags env) < 4+     then m env ref+     else collectProfileData+            (Text.unpackCString# mnPtr <> "." <> Text.unpackCString# nPtr)+            m env ref++-- | Collect profiling data and attribute it to given label.+collectProfileData+  :: ProfileLabel+  -> (Env u -> IORef (RequestStore u) -> IO (Result u a))+  -> Env u -> IORef (RequestStore u)+  -> IO (Result u a)+collectProfileData l m env ref = do+   a0 <- getAllocationCounter+   r <- m env{profLabel=l} ref -- what if it throws?+   a1 <- getAllocationCounter+   modifyProfileData env l (a0 - a1)+   -- So we do not count the allocation overhead of modifyProfileData+   setAllocationCounter a1+   case r of+     Done a -> return (Done a)+     Throw e -> return (Throw e)+     Blocked k -> return (Blocked (Cont (withLabel l (toHaxl k))))+{-# INLINE collectProfileData #-}++modifyProfileData :: Env u -> ProfileLabel -> AllocCount -> IO ()+modifyProfileData env label allocs =+  modifyIORef' (profRef env) $ \ p ->+    p { profile =+          HashMap.insertWith updEntry label newEntry .+          HashMap.insertWith updCaller caller newCaller $+          profile p }+  where caller = profLabel env+        newEntry =+          emptyProfileData+            { profileAllocs = allocs+            , profileDeps = HashSet.singleton caller }+        updEntry _ old =+          old { profileAllocs = profileAllocs old + allocs+              , profileDeps = HashSet.insert caller (profileDeps old) }+        -- subtract allocs from caller, so they are not double counted+        -- we don't know the caller's caller, but it will get set on+        -- the way back out, so an empty hashset is fine for now+        newCaller =+          emptyProfileData { profileAllocs = -allocs }+        updCaller _ old =+          old { profileAllocs = profileAllocs old - allocs }++incrementMemoHitCounterFor :: ProfileLabel -> Profile -> Profile+incrementMemoHitCounterFor lbl p =+  p { profile = HashMap.adjust incrementMemoHitCounter lbl (profile p) }++incrementMemoHitCounter :: ProfileData -> ProfileData+incrementMemoHitCounter pd = pd { profileMemoHits = succ (profileMemoHits pd) }+ -- ----------------------------------------------------------------------------- -- Exceptions @@ -294,7 +406,16 @@ throw e = GenHaxl $ \_env _ref -> raise e  raise :: (Exception e) => e -> IO (Result u a)-raise = return . Throw . toException+raise e+#ifdef PROFILING+  | Just (HaxlException Nothing h) <- fromException somex = do+    stk <- currentCallStack+    return (Throw (toException (HaxlException (Just stk) h)))+  | otherwise+#endif+    = return (Throw somex)+  where+    somex = toException e  -- | Catch an exception in the Haxl monad catch :: Exception e => GenHaxl u a -> (e -> GenHaxl u a) -> GenHaxl u a@@ -367,16 +488,37 @@   | Cached (Either SomeException a)  -- | Checks the data cache for the result of a request.-cached :: (Request r a) => Env u -> r a -> IO (CacheResult a)-cached env req = do-  cache <- readIORef (cacheRef env)+cached :: Request r a => Env u -> r a -> IO (CacheResult a)+cached = cachedWithInsert show DataCache.insert++-- | Show functions for request and its result.+type ShowReq r a = (r a -> String, a -> String)++-- Note [showFn]+--+-- Occasionally, for tracing purposes or generating exceptions, we need to+-- call 'show' on the request in a place where we *cannot* have a Show+-- dictionary. (Because the function is a worker which is called by one of+-- the *WithShow variants that take explicit show functions via a ShowReq+-- argument.) None of the functions that does this is exported, so this is+-- hidden from the Haxl user.++-- | Checks the data cache for the result of a request, inserting new results+-- with the given function.+cachedWithInsert+  :: Typeable (r a)+  => (r a -> String)    -- See Note [showFn]+  -> (r a -> ResultVar a -> DataCache ResultVar -> DataCache ResultVar) -> Env u+  -> r a -> IO (CacheResult a)+cachedWithInsert showFn insertFn env req = do   let-    do_fetch = do+    doFetch insertFn request cache = do       rvar <- newEmptyResult-      writeIORef (cacheRef env) $! DataCache.insert req rvar cache+      writeIORef (cacheRef env) $! insertFn request rvar cache       return (Uncached rvar)+  cache <- readIORef (cacheRef env)   case DataCache.lookup req cache of-    Nothing -> do_fetch+    Nothing -> doFetch insertFn req cache     Just rvar -> do       mb <- tryReadResult rvar       case mb of@@ -384,31 +526,94 @@         -- Use the cached result, even if it was an error.         Just r -> do           ifTrace (flags env) 3 $ putStrLn $ case r of-            Left _ -> "Cached error: " ++ show req-            Right _ -> "Cached request: " ++ show req+            Left _ -> "Cached error: " ++ showFn req+            Right _ -> "Cached request: " ++ showFn req           return (Cached r) +-- | Record the call stack for a data fetch in the Stats.  Only useful+-- when profiling.+logFetch :: Env u -> (r a -> String) -> r a -> IO ()+#ifdef PROFILING+logFetch env showFn req = do+  ifReport (flags env) 5 $ do+    stack <- currentCallStack+    modifyIORef' (statsRef env) $ \(Stats s) ->+      Stats (FetchCall (showFn req) stack : s)+#else+logFetch _ _ _ = return ()+#endif+ -- | Performs actual fetching of data for a 'Request' from a 'DataSource'. dataFetch :: (DataSource u r, Request r a) => r a -> GenHaxl u a-dataFetch req = GenHaxl $ \env ref -> do+dataFetch = dataFetchWithInsert show DataCache.insert++-- | Performs actual fetching of data for a 'Request' from a 'DataSource', using+-- the given show functions for requests and their results.+dataFetchWithShow+  :: (DataSource u r, Eq (r a), Hashable (r a), Typeable (r a))+  => ShowReq r a+  -> r a -> GenHaxl u a+dataFetchWithShow (showReq, showRes) = dataFetchWithInsert showReq+  (DataCache.insertWithShow showReq showRes)++-- | Performs actual fetching of data for a 'Request' from a 'DataSource', using+-- the given function to insert requests in the cache.+dataFetchWithInsert+  :: (DataSource u r, Eq (r a), Hashable (r a), Typeable (r a))+  => (r a -> String)    -- See Note [showFn]+  -> (r a -> ResultVar a -> DataCache ResultVar -> DataCache ResultVar)+  -> r a+  -> GenHaxl u a+dataFetchWithInsert showFn insertFn req = GenHaxl $ \env ref -> do   -- First, check the cache-  res <- cached env req+  res <- cachedWithInsert showFn insertFn env req+  ifProfiling (flags env) $ addProfileFetch env req   case res of     -- Not seen before: add the request to the RequestStore, so it     -- will be fetched in the next round.     Uncached rvar -> do+      logFetch env showFn req       modifyIORef' ref $ \bs -> addRequest (BlockedFetch req rvar) bs-      return $ Blocked (Cont (continueFetch req rvar))+      return $ Blocked (Cont (continueFetch showFn req rvar))      -- Seen before but not fetched yet.  We're blocked, but we don't have     -- to add the request to the RequestStore.-    CachedNotFetched rvar -> return-      $ Blocked (Cont (continueFetch req rvar))+    CachedNotFetched rvar ->+      return (Blocked (Cont (continueFetch showFn req rvar)))      -- Cached: either a result, or an exception     Cached (Left ex) -> return (Throw ex)     Cached (Right a) -> return (Done a) +{-# NOINLINE addProfileFetch #-}+addProfileFetch+  :: (DataSourceName r, Eq (r a), Hashable (r a), Typeable (r a))+  => Env u -> r a -> IO ()+addProfileFetch env req = do+  c <- getAllocationCounter+  modifyIORef' (profRef env) $ \ p ->+    let+      dsName :: Text.Text+      dsName = dataSourceName req++      upd :: Round -> ProfileData -> ProfileData+      upd round d =+        d { profileFetches = Map.alter (Just . f) round (profileFetches d) }++      f Nothing   = HashMap.singleton dsName 1+      f (Just hm) = HashMap.insertWith (+) dsName 1 hm+    in case DataCache.lookup req (profileCache p) of+        Nothing ->+          let r = profileRound p+          in p { profile = HashMap.adjust (upd r) (profLabel env) (profile p)+               , profileCache =+                  DataCache.insertNotShowable req (Constant r) (profileCache p)+               }+        Just (Constant r) ->+          p { profile = HashMap.adjust (upd r) (profLabel env) (profile p) }+  -- So we do not count the allocation overhead of addProfileFetch+  setAllocationCounter c+ -- | A data request that is not cached.  This is not what you want for -- normal read requests, because then multiple identical requests may -- return different results, and this invalidates some of the@@ -420,20 +625,20 @@ -- are done in a safe way - that is, not mixed with reads that might -- conflict in the same Haxl computation. ---uncachedRequest :: (DataSource u r, Request r a) => r a -> GenHaxl u a+uncachedRequest :: (DataSource u r, Show (r a)) => r a -> GenHaxl u a uncachedRequest req = GenHaxl $ \_env ref -> do   rvar <- newEmptyResult   modifyIORef' ref $ \bs -> addRequest (BlockedFetch req rvar) bs-  return $ Blocked (Cont (continueFetch req rvar))+  return $ Blocked (Cont (continueFetch show req rvar))  continueFetch-  :: (DataSource u r, Request r a, Show a)-  => r a -> ResultVar a -> GenHaxl u a-continueFetch req rvar = GenHaxl $ \_env _ref -> do+  :: (r a -> String)    -- See Note [showFn]+  -> r a -> ResultVar a -> GenHaxl u a+continueFetch showFn req rvar = GenHaxl $ \_env _ref -> do   m <- tryReadResult rvar   case m of     Nothing -> raise . DataSourceError $-      textShow req <> " did not set contents of result var"+      Text.pack (showFn req) <> " did not set contents of result var"     Just r -> done r  -- | Transparently provides caching. Useful for datasources that can@@ -441,9 +646,26 @@ -- the IO operation (except for asynchronous exceptions) are -- propagated into the Haxl monad and can be caught by 'catch' and -- 'try'.-cacheResult :: (Request r a)  => r a -> IO a -> GenHaxl u a-cacheResult req val = GenHaxl $ \env _ref -> do-  cachedResult <- cached env req+cacheResult :: Request r a => r a -> IO a -> GenHaxl u a+cacheResult = cacheResultWithInsert show DataCache.insert++-- | Transparently provides caching in the same way as 'cacheResult', but uses+-- the given functions to show requests and their results.+cacheResultWithShow+  :: (Eq (r a), Hashable (r a), Typeable (r a))+  => ShowReq r a -> r a -> IO a -> GenHaxl u a+cacheResultWithShow (showReq, showRes) = cacheResultWithInsert showReq+  (DataCache.insertWithShow showReq showRes)++-- Transparently provides caching, using the given function to insert requests+-- into the cache.+cacheResultWithInsert+  :: Typeable (r a)+  => (r a -> String)    -- See Note [showFn]+  -> (r a -> ResultVar a -> DataCache ResultVar -> DataCache ResultVar) -> r a+  -> IO a -> GenHaxl u a+cacheResultWithInsert showFn insertFn req val = GenHaxl $ \env _ref -> do+  cachedResult <- cachedWithInsert showFn insertFn env req   case cachedResult of     Uncached rvar -> do       result <- Exception.try val@@ -455,14 +677,13 @@     CachedNotFetched _ -> corruptCache   where     corruptCache = raise . DataSourceError $ Text.concat-      [ textShow req+      [ Text.pack (showFn req)       , " has a corrupted cache value: these requests are meant to"       , " return immediately without an intermediate value. Either"       , " the cache was updated incorrectly, or you're calling"       , " cacheResult on a query that involves a blocking fetch."       ] - -- We must be careful about turning IO monad exceptions into Haxl -- exceptions.  An IO monad exception will normally propagate right -- out of runHaxl and terminate the whole computation, whereas a Haxl@@ -510,7 +731,7 @@ -- deterministic. -- cacheRequest-  :: (Request req a) => req a -> Either SomeException a -> GenHaxl u ()+  :: Request req a => req a -> Either SomeException a -> GenHaxl u () cacheRequest request result = GenHaxl $ \env _ref -> do   res <- cached env request   case res of@@ -539,6 +760,7 @@       !n' = n + length jobs    t0 <- getCurrentTime+  a0 <- getAllocationCounter    let     roundstats =@@ -573,32 +795,52 @@    fetches <- mapM applyFetch $ zip [n..] jobs -  times <-+  deepStats <-     if report f >= 2     then do-      (refs, timedfetches) <- mapAndUnzipM wrapFetchInTimer fetches+      (refs, timedfetches) <- mapAndUnzipM wrapFetchInStats fetches       scheduleFetches timedfetches       mapM (fmap Just . readIORef) refs     else do       scheduleFetches fetches       return $ repeat Nothing +  failures <-+    if report f >= 3+    then+      forM jobs $ \(BlockedFetches reqs) ->+        fmap (Just . length) . flip filterM reqs $ \(BlockedFetch _ rvar) -> do+          mb <- tryReadResult rvar+          return $ case mb of+            Just (Right _) -> False+            _ -> True+    else return $ repeat Nothing+   let dsroundstats = HashMap.fromList-         [ (name, DataSourceRoundStats { dataSourceFetches = fetches-                                       , dataSourceTime = time+         [ (name, DataSourceRoundStats { dataSourceFetches = dsfetch+                                       , dataSourceTime = fst <$> dsStats+                                       , dataSourceAllocation = snd <$> dsStats+                                       , dataSourceFailures = dsfailure                                        })-         | ((name, fetches), time) <- zip roundstats times]+         | ((name, dsfetch), dsStats, dsfailure) <-+             zip3 roundstats deepStats failures] +  a1 <- getAllocationCounter   t1 <- getCurrentTime-  let roundtime = realToFrac (diffUTCTime t1 t0) :: Double+  let+    roundtime = realToFrac (diffUTCTime t1 t0) :: Double+    allocation = fromIntegral $ a0 - a1    ifReport f 1 $     modifyIORef' sref $ \(Stats rounds) -> roundstats `deepseq`-      Stats (RoundStats (microsecs roundtime) dsroundstats: rounds)+      Stats (RoundStats (microsecs roundtime) allocation dsroundstats: rounds)    ifTrace f 1 $     printf "Batch data fetch done (%.2fs)\n" (realToFrac roundtime :: Double) +  ifProfiling f $+    modifyIORef' (profRef env) $ \ p -> p { profileRound = 1 + profileRound p }+   return n'  -- Catch exceptions arising from the data source and stuff them into@@ -625,17 +867,24 @@       void $ tryTakeResult rvar       putResult rvar (except e) -wrapFetchInTimer :: PerformFetch -> IO (IORef Microseconds, PerformFetch)-wrapFetchInTimer f = do-  r <- newIORef 0+wrapFetchInStats :: PerformFetch -> IO (IORef (Microseconds, Int), PerformFetch)+wrapFetchInStats f = do+  r <- newIORef (0, 0)   case f of-    SyncFetch io -> return (r, SyncFetch (time io >>= writeIORef r))+    SyncFetch io -> return (r, SyncFetch (statsForIO io >>= writeIORef r))     AsyncFetch f -> do-       inner_r <- newIORef 0+       inner_r <- newIORef (0, 0)        return (r, AsyncFetch $ \inner -> do-         total <- time (f (time inner >>= writeIORef inner_r))-         inner_t <- readIORef inner_r-         writeIORef r (total - inner_t))+         (totalTime, totalAlloc) <-+           statsForIO (f (statsForIO inner >>= writeIORef inner_r))+         (innerTime, innerAlloc) <- readIORef inner_r+         writeIORef r (totalTime - innerTime, totalAlloc - innerAlloc))+  where+    statsForIO io = do+      prevAlloc <- getAllocationCounter+      t <- time io+      postAlloc <- getAllocationCounter+      return (t, fromIntegral $ prevAlloc - postAlloc)  wrapFetchInTrace :: Int -> Int -> Text.Text -> PerformFetch -> PerformFetch #ifdef EVENTLOG@@ -680,19 +929,47 @@ -- ----------------------------------------------------------------------------- -- Memoization --- | A variable in the cache representing the state of a memoized computation+-- | Variables representing memoized computations. newtype MemoVar u a = MemoVar (IORef (MemoStatus u a))+newtype MemoVar1 u a b = MemoVar1 (IORef (MemoStatus1 u a b))+newtype MemoVar2 u a b c = MemoVar2 (IORef (MemoStatus2 u a b c))  -- | The state of a memoized computation data MemoStatus u a+  -- | Memoized computation under evaluation. The memo was last evaluated during+  -- the given round, or never, if the given round is Nothing. The continuation+  -- might be slightly out of date, but that's fine; the worst that can happen+  -- is we do a little extra work.   = MemoInProgress (RoundId u) (GenHaxl u a)-      -- ^ Under evaluation in the given round, here is the latest-      -- continuation.  The continuation might be a little out of-      -- date, but that's fine, the worst that can happen is we do a-      -- little extra work.++  -- | A fully evaluated memo; here is the result.   | MemoDone (Either SomeException a)-      -- fully evaluated, here is the result. +  -- | A new memo, with a stored computation. Not empty, but has not been run+  -- yet.+  | MemoNew (GenHaxl u a)++  -- | An empty memo, should not be run before preparation.+  | MemoEmpty++-- | The state of a memoized 1-argument function.+data MemoStatus1 u a b+  -- | An unprepared memo.+  = MemoEmpty1+  -- | A memo-table containing @MemoStatus@es for at least one in-progress memo.+  | MemoTbl1 ( a -> GenHaxl u b+             , HashMap.HashMap a+               (MemoVar u b))++data MemoStatus2 u a b c+  -- | An unprepared memo.+  = MemoEmpty2+  -- | A memo-table containing @MemoStatus@es for at least one in-progress memo.+  | MemoTbl2 ( a -> b -> GenHaxl u c+             , HashMap.HashMap a+               (HashMap.HashMap b+                 (MemoVar u c)))+ type RoundId u = IORef (RequestStore u) {- Note [RoundId]@@ -713,66 +990,28 @@ -- cachedComputation    :: forall req u a.-      (Eq (req a)+      ( Eq (req a)       , Hashable (req a)       , Typeable (req a))    => req a -> GenHaxl u a -> GenHaxl u a--cachedComputation req haxl = GenHaxl $ \env ref -> do-  cache <- readIORef (memoRef env)-  case DataCache.lookup req cache of-    Nothing -> do-      memovar <- newIORef (MemoInProgress ref haxl)-      writeIORef (memoRef env) $!-        DataCache.insertNotShowable req (MemoVar memovar) cache-      run memovar haxl env ref-    Just (MemoVar memovar) -> do-      status <- readIORef memovar-      case status of-        MemoDone r -> done r-        MemoInProgress round cont-          | round == ref -> return (Blocked (Cont (retryMemo req)))-          | otherwise    -> run memovar cont env ref-          -- was blocked in a previous round; run the saved continuation to-          -- make more progress.- where-  -- If we got blocked on this memo in the current round, this is the-  -- continuation: just try to evaluate the memo again.  We know it is-  -- already in the cache (because we just checked), so the computation-  -- will never be used.-  retryMemo req =-   cachedComputation req (throw (CriticalError "retryMemo"))--  -- Run the memoized computation and store the result (complete or-  -- partial) back in the MemoVar afterwards.- --- -- We don't attempt to catch IO monad exceptions here.  That may seem- -- dangerous, because if an IO exception is raised we'll leave the- -- MemoInProgress in the MemoVar.  But we always want to just- -- propagate an IO monad exception (it should kill the whole runHaxl,- -- unless there's a unsafeToHaxlException), so we should never be- -- looking at the MemoVar again anyway.  Furthermore, storing the- -- exception in the MemoVar is wrong, because that will turn it into- -- a Haxl exception (see rethrowAsyncExceptions).-  run memovar cont env ref = do-    e <- unHaxl cont env ref-    case e of-      Done a -> complete memovar (Right a)-      Throw e -> complete memovar (Left e)-      Blocked cont -> do-        writeIORef memovar (MemoInProgress ref (toHaxl cont))-        return (Blocked (Cont (retryMemo req)))--  -- We're finished: store the final result-  complete memovar r = do-    writeIORef memovar (MemoDone r)-    done r+cachedComputation req haxl = do+  env <- env id+  cache <- unsafeLiftIO $ readIORef (memoRef env)+  unsafeLiftIO $ ifProfiling (flags env) $+    modifyIORef' (profRef env) (incrementMemoHitCounterFor (profLabel env))+  memoVar <- case DataCache.lookup req cache of+               Nothing -> do+                 memoVar <- newMemoWith haxl+                 unsafeLiftIO $ writeIORef (memoRef env) $!+                   DataCache.insertNotShowable req memoVar cache+                 return memoVar+               Just memoVar -> return memoVar+  runMemo memoVar  -- | Lifts an 'Either' into either 'Throw' or 'Done'. done :: Either SomeException a -> IO (Result u a) done = return . either Throw Done - -- -----------------------------------------------------------------------------  -- | Dump the contents of the cache as Haskell code that, when@@ -785,7 +1024,14 @@ -- >   cacheRequest (CountAardvarks "abcabc") (Right (2)) -- dumpCacheAsHaskell :: GenHaxl u String-dumpCacheAsHaskell = do+dumpCacheAsHaskell = dumpCacheAsHaskellFn "loadCache" "GenHaxl u ()"++-- | Dump the contents of the cache as Haskell code that, when+-- compiled and run, will recreate the same cache contents.+--+-- Takes the name and type for the resulting function as arguments.+dumpCacheAsHaskellFn :: String -> String -> GenHaxl u String+dumpCacheAsHaskellFn fnName fnType = do   ref <- env cacheRef  -- NB. cacheRef, not memoRef.  We ignore memoized                        -- results when dumping the cache.   entries <- unsafeLiftIO $ readIORef ref >>= showCache@@ -796,7 +1042,166 @@     result (Right s) = text "Right" <+> parens (text s)    return $ show $-    text "loadCache :: GenHaxl u ()" $$-    text "loadCache = do" $$+    text (fnName ++ " :: " ++ fnType) $$+    text (fnName ++ " = do") $$       nest 2 (vcat (map mk_cr (concatMap snd entries))) $$     text "" -- final newline++-- | Create a new @MemoVar@ for storing a memoized computation. The created+-- @MemoVar@ is initially empty, not tied to any specific computation. Running+-- this memo (with @runMemo@) without preparing it first (with @prepareMemo@)+-- will result in an exception.+newMemo :: GenHaxl u (MemoVar u a)+newMemo = unsafeLiftIO $ MemoVar <$> newIORef MemoEmpty++-- | Store a computation within a supplied @MemoVar@. Any memo stored within the+-- @MemoVar@ already (regardless of completion) will be discarded, in favor of+-- the supplied computation. A @MemoVar@ must be prepared before it is run.+prepareMemo :: MemoVar u a -> GenHaxl u a -> GenHaxl u ()+prepareMemo (MemoVar memoRef) memoCmp+  = unsafeLiftIO $ writeIORef memoRef (MemoNew memoCmp)++-- | Convenience function, combines @newMemo@ and @prepareMemo@.+newMemoWith :: GenHaxl u a -> GenHaxl u (MemoVar u a)+newMemoWith memoCmp = do+  memoVar <- newMemo+  prepareMemo memoVar memoCmp+  return memoVar++-- | Continue the memoized computation within a given @MemoVar@.+-- Notes:+--+--   1. If the memo contains a complete result, return that result.+--   2. If the memo contains an in-progress computation, continue it as far as+--      possible for this round.+--   3. If the memo is empty (it was not prepared), throw an error.+--+-- For example, to memoize the computation @one@ given by:+--+-- > one :: Haxl Int+-- > one = return 1+--+-- use:+--+-- > do+-- >   oneMemo <- newMemoWith one+-- >   let memoizedOne = runMemo aMemo one+-- >   oneResult <- memoizedOne+--+-- To memoize mutually dependent computations such as in:+--+-- > h :: Haxl Int+-- > h = do+-- >   a <- f+-- >   b <- g+-- >   return (a + b)+-- >  where+-- >   f = return 42+-- >   g = succ <$> f+--+-- without needing to reorder them, use:+--+-- > h :: Haxl Int+-- > h = do+-- >   fMemoRef <- newMemo+-- >   gMemoRef <- newMemo+-- >+-- >   let f = runMemo fMemoRef+-- >       g = runMemo gMemoRef+-- >+-- >   prepareMemo fMemoRef $ return 42+-- >   prepareMemo gMemoRef $ succ <$> f+-- >+-- >   a <- f+-- >   b <- g+-- >   return (a + b)+--+runMemo :: MemoVar u a -> GenHaxl u a+runMemo memoVar@(MemoVar memoRef) = GenHaxl $ \env rID ->+  readIORef memoRef >>= \case+    -- Memo was not prepared first; throw an exception.+    MemoEmpty -> raise $ CriticalError "Attempting to run empty memo."+    -- The memo is complete.+    MemoDone result -> done result+    -- Memo has just been prepared, run it.+    MemoNew cont -> runContToMemo cont env rID+    -- The memo is in progress, there *may* be progress to be made.+    MemoInProgress rID' cont+      -- The last update was performed *this* round and is still in progress;+      -- nothing further can be done this round. Wait until the next round.+      | rID' == rID -> return (Blocked $ Cont retryMemo)+      -- This is the first time this memo is being run during this round, or+      -- at all. Enough progress may have been made to continue running the+      -- memo.+      | otherwise -> runContToMemo cont env rID+ where+  -- Continuation to retry an existing memo. It is not possible to *retry* an+  -- empty memo; that will throw an exception during the next round.+  retryMemo = runMemo memoVar++  -- Run a continuation, and store the result in the memo reference. Any+  -- exceptions thrown during the running of the memo are thrown directly; they+  -- are also stored in the memoVar just in case, but we shouldn't be looking at+  -- the memoVar again anyway.+  --+  -- If the memo is incomplete by the end of this round, update its progress+  -- indicator and block.+  runContToMemo cont env rID = do+    result <- unHaxl cont env rID+    case result of+      Done a -> finalize (Right a)+      Throw e -> finalize (Left e)+      Blocked c -> do+        writeIORef memoRef (MemoInProgress rID (toHaxl c))+        return (Blocked $ Cont retryMemo)++  finalize r = writeIORef memoRef (MemoDone r) >> done r++newMemo1 :: GenHaxl u (MemoVar1 u a b)+newMemo1 = unsafeLiftIO $ MemoVar1 <$> newIORef MemoEmpty1++newMemoWith1 :: (a -> GenHaxl u b) -> GenHaxl u (MemoVar1 u a b)+newMemoWith1 f = newMemo1 >>= \r -> prepareMemo1 r f >> return r++prepareMemo1 :: MemoVar1 u a b -> (a -> GenHaxl u b) -> GenHaxl u ()+prepareMemo1 (MemoVar1 r) f+  = unsafeLiftIO $ writeIORef r (MemoTbl1 (f, HashMap.empty))++runMemo1 :: (Eq a, Hashable a) => MemoVar1 u a b -> a -> GenHaxl u b+runMemo1 (MemoVar1 r) k = unsafeLiftIO (readIORef r) >>= \case+  MemoEmpty1 -> throw $ CriticalError "Attempting to run empty memo."+  MemoTbl1 (f, h) -> case HashMap.lookup k h of+    Nothing -> do+      x <- newMemoWith (f k)+      unsafeLiftIO $ writeIORef r (MemoTbl1 (f, HashMap.insert k x h))+      runMemo x+    Just v -> runMemo v++newMemo2 :: GenHaxl u (MemoVar2 u a b c)+newMemo2 = unsafeLiftIO $ MemoVar2 <$> newIORef MemoEmpty2++newMemoWith2 :: (a -> b -> GenHaxl u c) -> GenHaxl u (MemoVar2 u a b c)+newMemoWith2 f = newMemo2 >>= \r -> prepareMemo2 r f >> return r++prepareMemo2 :: MemoVar2 u a b c -> (a -> b -> GenHaxl u c) -> GenHaxl u ()+prepareMemo2 (MemoVar2 r) f+  = unsafeLiftIO $ writeIORef r (MemoTbl2 (f, HashMap.empty))++runMemo2 :: (Eq a, Hashable a, Eq b, Hashable b)+         => MemoVar2 u a b c+         -> a -> b -> GenHaxl u c+runMemo2 (MemoVar2 r) k1 k2 = unsafeLiftIO (readIORef r) >>= \case+  MemoEmpty2 -> throw $ CriticalError "Attempting to run empty memo."+  MemoTbl2 (f, h1) -> case HashMap.lookup k1 h1 of+    Nothing -> do+      v <- newMemoWith (f k1 k2)+      unsafeLiftIO $ writeIORef r+        (MemoTbl2 (f, HashMap.insert k1 (HashMap.singleton k2 v) h1))+      runMemo v+    Just h2 -> case HashMap.lookup k2 h2 of+      Nothing -> do+        v <- newMemoWith (f k1 k2)+        unsafeLiftIO $ writeIORef r+          (MemoTbl2 (f, HashMap.insert k1 (HashMap.insert k2 v h2) h1))+        runMemo v+      Just v -> runMemo v
Haxl/Core/Types.hs view
@@ -26,18 +26,30 @@   defaultFlags,   ifTrace,   ifReport,+  ifProfiling,    -- * Statistics   Stats(..),   RoundStats(..),   DataSourceRoundStats(..),   Microseconds,+  Round,   emptyStats,   numRounds,   numFetches,   ppStats,   ppRoundStats,   ppDataSourceRoundStats,+  Profile,+  emptyProfile,+  profile,+  profileRound,+  profileCache,+  ProfileLabel,+  ProfileData(..),+  emptyProfileData,+  AllocCount,+  MemoHitCount,    -- * Data fetching   DataSource(..),@@ -46,6 +58,11 @@   BlockedFetch(..),   PerformFetch(..), +  -- * DataCache+  DataCache(..),+  SubCache(..),+  emptyDataCache,+   -- * Result variables   ResultVar(..),   newEmptyResult,@@ -76,12 +93,18 @@ import Control.Monad import Data.Aeson import Data.Function (on)+import Data.Functor.Constant+import Data.Int import Data.Hashable import Data.HashMap.Strict (HashMap, toList) import qualified Data.HashMap.Strict as HashMap+import Data.HashSet (HashSet)+import qualified Data.HashSet as HashSet import Data.List (intercalate, sortBy)+import Data.Map (Map)+import qualified Data.Map as Map import Data.Text (Text, unpack)-import Data.Typeable (Typeable)+import Data.Typeable.Internal  #if __GLASGOW_HASKELL__ < 708 import Haxl.Core.Util (tryReadMVar)@@ -89,18 +112,27 @@ import Haxl.Core.Show1 import Haxl.Core.StateStore +-- ---------------------------------------------------------------------------+-- Flags+ -- | Flags that control the operation of the engine. data Flags = Flags-  { trace :: Int+  { trace :: {-# UNPACK #-} !Int     -- ^ Tracing level (0 = quiet, 3 = very verbose).-  , report :: Int-    -- ^ Report level (0 = quiet, 1 = # of requests, 2 = time, 3 = # of errors)+  , report :: {-# UNPACK #-} !Int+    -- ^ Report level (0 = quiet, 1 = # of requests, 2 = time, 3 = # of errors,+    -- 4 = profiling, 5 = log stack traces of dataFetch calls)+  , caching :: {-# UNPACK #-} !Int+    -- ^ Non-zero if caching is enabled.  If caching is disabled, then+    -- we still do batching and de-duplication within a round, but do+    -- not cache results between rounds.   }  defaultFlags :: Flags defaultFlags = Flags   { trace = 0   , report = 1+  , caching = 1   }  -- | Runs an action if the tracing level is above the given threshold.@@ -111,69 +143,168 @@ ifReport :: (Functor m, Monad m) => Flags -> Int -> m a -> m () ifReport flags i = when (report flags >= i) . void +ifProfiling :: (Functor m, Monad m) => Flags -> m a -> m ()+ifProfiling flags = when (report flags >= 4) . void++-- ---------------------------------------------------------------------------+-- Stats+ type Microseconds = Int+-- | Rounds are 1-indexed+type Round = Int  -- | Stats that we collect along the way. newtype Stats = Stats [RoundStats]-  deriving ToJSON+  deriving (Show, ToJSON)  -- | Pretty-print Stats. ppStats :: Stats -> String ppStats (Stats rss) =-  intercalate "\n" [ "Round: " ++ show i ++ " - " ++ ppRoundStats rs-                   | (i, rs) <- zip [(1::Int)..] (reverse rss) ]+  intercalate "\n"+     [ "Round: " ++ show i ++ " - " ++ ppRoundStats rs+     | (i, rs) <- zip [(1::Int)..] (filter isRoundStats (reverse rss)) ]+ where+  isRoundStats RoundStats{} = True+  isRoundStats _ = False  -- | Maps data source name to the number of requests made in that round. -- The map only contains entries for sources that made requests in that -- round.-data RoundStats = RoundStats-  { roundTime :: Microseconds-  , roundDataSources :: HashMap Text DataSourceRoundStats-  }+data RoundStats+    -- | Timing stats for a round of data fetching+  = RoundStats+    { roundTime :: Microseconds+    , roundAllocation :: Int+    , roundDataSources :: HashMap Text DataSourceRoundStats+    }+    -- | The stack trace of a call to 'dataFetch'.  These are collected+    -- only when profiling and reportLevel is 5 or greater.+  | FetchCall+    { fetchReq :: String+    , fetchStack :: [String]+    }+  deriving (Show)  -- | Pretty-print RoundStats. ppRoundStats :: RoundStats -> String-ppRoundStats (RoundStats t dss) =-    show t ++ "us\n"+ppRoundStats (RoundStats t a dss) =+    show t ++ "us " ++ show a ++ " bytes\n"       ++ unlines [ "  " ++ unpack nm ++ ": " ++ ppDataSourceRoundStats dsrs                  | (nm, dsrs) <- sortBy (compare `on` fst) (toList dss) ]+ppRoundStats (FetchCall r ss) = show r ++ '\n':show ss  instance ToJSON RoundStats where   toJSON RoundStats{..} = object     [ "time" .= roundTime+    , "allocation" .= roundAllocation     , "dataSources" .= roundDataSources     ]+  toJSON (FetchCall req strs) = object+    [ "request" .= req+    , "stack" .= strs+    ]  -- | Detailed stats of each data source in each round. data DataSourceRoundStats = DataSourceRoundStats   { dataSourceFetches :: Int   , dataSourceTime :: Maybe Microseconds-  }+  , dataSourceFailures :: Maybe Int+  , dataSourceAllocation :: Maybe Int+  } deriving (Show)  -- | Pretty-print DataSourceRoundStats ppDataSourceRoundStats :: DataSourceRoundStats -> String-ppDataSourceRoundStats (DataSourceRoundStats i t) =-  maybeTime $ show i ++ " fetches"-    where maybeTime = maybe id (\ tm s -> s ++ " (" ++ show tm ++ "us)") t+ppDataSourceRoundStats (DataSourceRoundStats fetches time failures allocs) =+  maybe id (\t s -> s ++ " (" ++ show t ++ "us)") time $+  maybe id (\a s -> s ++ " (" ++ show a ++ " bytes)") allocs $+  maybe id (\f s -> s ++ " " ++ show f ++ " failures") failures $+  show fetches ++ " fetches"  instance ToJSON DataSourceRoundStats where   toJSON DataSourceRoundStats{..} = object [k .= v | (k, Just v) <-     [ ("fetches", Just dataSourceFetches)     , ("time", dataSourceTime)+    , ("failures", dataSourceFailures)+    , ("allocation", dataSourceAllocation)     ]]  fetchesInRound :: RoundStats -> Int-fetchesInRound (RoundStats _ hm) =+fetchesInRound (RoundStats _ _ hm) =   sum $ map dataSourceFetches $ HashMap.elems hm+fetchesInRound _ = 0  emptyStats :: Stats emptyStats = Stats []  numRounds :: Stats -> Int-numRounds (Stats rs) = length rs+numRounds (Stats rs) = length [ s | s@RoundStats{} <- rs ]  numFetches :: Stats -> Int numFetches (Stats rs) = sum (map fetchesInRound rs)+++-- ---------------------------------------------------------------------------+-- Profiling++type ProfileLabel = Text+type AllocCount = Int64+type MemoHitCount = Int64++data Profile = Profile+  { profileRound :: {-# UNPACK #-} !Round+     -- ^ Keep track of what the current fetch round is.+  , profile      :: HashMap ProfileLabel ProfileData+     -- ^ Data on individual labels.+  , profileCache :: DataCache (Constant Round)+     -- ^ Keep track of the round requests first appear in.+  }++emptyProfile :: Profile+emptyProfile = Profile 1 HashMap.empty emptyDataCache++data ProfileData = ProfileData+  { profileAllocs :: {-# UNPACK #-} !AllocCount+     -- ^ allocations made by this label+  , profileDeps :: HashSet ProfileLabel+     -- ^ labels that this label depends on+  , profileFetches :: Map Round (HashMap Text Int)+     -- ^ map from round to {datasource name => fetch count}+  , profileMemoHits :: {-# UNPACK #-} !MemoHitCount+    -- ^ number of hits to memoized computation at this label+  }+  deriving Show++emptyProfileData :: ProfileData+emptyProfileData = ProfileData 0 HashSet.empty Map.empty 0++-- ---------------------------------------------------------------------------+-- DataCache++-- | The 'DataCache' maps things of type @f a@ to @'ResultVar' a@, for+-- any @f@ and @a@ provided @f a@ is an instance of 'Typeable'. In+-- practice @f a@ will be a request type parameterised by its result.+--+-- See the definition of 'ResultVar' for more details.++newtype DataCache res = DataCache (HashMap TypeRep (SubCache res))++-- | The implementation is a two-level map: the outer level maps the+-- types of requests to 'SubCache', which maps actual requests to their+-- results.  So each 'SubCache' contains requests of the same type.+-- This works well because we only have to store the dictionaries for+-- 'Hashable' and 'Eq' once per request type.+data SubCache res =+  forall req a . (Hashable (req a), Eq (req a), Typeable (req a)) =>+       SubCache (req a -> String) (a -> String) ! (HashMap (req a) (res a))+       -- NB. the inner HashMap is strict, to avoid building up+       -- a chain of thunks during repeated insertions.++-- | A new, empty 'DataCache'.+emptyDataCache :: DataCache res+emptyDataCache = DataCache HashMap.empty++-- ---------------------------------------------------------------------------+-- DataSource class  -- | The class of data sources, parameterised over the request type for -- that data source. Every data source must implement this class.
Haxl/Prelude.hs view
@@ -33,6 +33,7 @@      -- * Haxl and Fetching data     GenHaxl, dataFetch, DataSource, memo,+    memoize, memoize1, memoize2,      -- * Extra Monad and Applicative things     Applicative(..),@@ -70,7 +71,7 @@ import Haxl.Core.Monad  import Control.Applicative-import Control.Monad (foldM, join)+import Control.Monad (foldM, join, void) import Data.List (foldl', sort) import Data.Text (Text) import Data.Traversable hiding (forM, mapM, sequence)@@ -101,7 +102,7 @@ -- The equality constraint is necessary to convince the typechecker that -- this is valid: ----- > if getCountry ip .== "us" then ... else ...+-- > if ipGetCountry ip .== "us" then ... else ... -- instance (u1 ~ u2) => IfThenElse (GenHaxl u1 Bool) (GenHaxl u2 a) where   ifThenElse fb t e = do@@ -174,7 +175,7 @@  -- | See 'mapM'. mapM_ :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f ()-mapM_ f t = const () <$> traverse f t+mapM_ f t = void $ traverse f t  forM_ :: (Traversable t, Applicative f) => t a -> (a -> f b) -> f () forM_ = flip mapM_@@ -185,7 +186,7 @@  -- | See 'mapM'. sequence_ :: (Traversable t, Applicative f) => t (f a) -> f ()-sequence_ t = const () <$> sequenceA t+sequence_ t = void $ sequenceA t  -- | See 'mapM'. filterM :: (Applicative f, Monad f) => (a -> f Bool) -> [a] -> f [a]
haxl.cabal view
@@ -1,5 +1,5 @@ name:                haxl-version:             0.3.1.0+version:             0.4.0.0 synopsis:            A Haskell library for efficient, concurrent,                      and concise data access. homepage:            https://github.com/facebook/Haxl@@ -13,6 +13,10 @@ build-type:          Simple stability:           alpha cabal-version:       >= 1.10+tested-with:+  GHC==7.8.4,+  GHC==7.10.3,+  GHC==8.0.1  description:   Haxl is a library and EDSL for efficient scheduling of concurrent data@@ -39,18 +43,21 @@    build-depends:     HUnit >= 1.2 && < 1.4,-    aeson >= 0.6 && < 0.11,+    aeson >= 0.6 && < 1.1,     base == 4.*,+    binary >= 0.7 && < 0.9,     bytestring >= 0.9 && < 0.11,     containers == 0.5.*,     deepseq,     directory >= 1.1 && < 1.3,     exceptions >=0.8 && <0.9,     filepath >= 1.3 && < 1.5,+    ghc-prim,     hashable == 1.2.*,     pretty == 1.1.*,     text >= 1.1.0.1 && < 1.3,-    time >= 1.4 && < 1.6,+    time >= 1.4 && < 1.7,+    transformers,     unordered-containers == 0.2.*,     vector >= 0.10 && <0.12 @@ -58,12 +65,12 @@     Haxl.Core,     Haxl.Core.DataCache,     Haxl.Core.Exception,+    Haxl.Core.Memo,     Haxl.Core.Monad,     Haxl.Core.RequestStore,     Haxl.Core.StateStore,     Haxl.Core.Show1,     Haxl.Core.Types,-    Haxl.Core.Memo,     Haxl.Prelude    other-modules:@@ -82,10 +89,15 @@     HUnit,     aeson,     base == 4.*,+    binary,     bytestring,     containers,+    deepseq,+    filepath,     hashable,     haxl,+    test-framework,+    test-framework-hunit,     text,     unordered-containers @@ -98,7 +110,7 @@     tests    main-is:-    Main.hs+    TestMain.hs    other-modules:     BatchTests
readme.md view
@@ -39,9 +39,11 @@  * [The N+1 Selects Problem](example/sql/readme.md) explains how Haxl    can address a common performance problem with SQL queries by    automatically batching multiple queries into a single query,-   completely invisibly to the programmer.+   without the programmer having to specify this behavior.   * [Haxl Documentation](http://hackage.haskell.org/package/haxl) on    Hackage.   * [There is no Fork: An Abstraction for Efficient, Concurrent, and Concise Data Access](http://community.haskell.org/~simonmar/papers/haxl-icfp14.pdf), our paper on Haxl, accepted for publication at ICFP'14.++[![Build Status](https://travis-ci.org/facebook/Haxl.svg?branch=master)](https://travis-ci.org/facebook/Haxl)
tests/BatchTests.hs view
@@ -12,6 +12,7 @@  import Prelude() import Haxl.Prelude+import Data.IORef  -- ----------------------------------------------------------------------------- @@ -138,6 +139,15 @@   -- ensure no more data fetching rounds needed   expectRoundsWithEnv 0 12 batching7_ env2 +noCaching = do+  env <- makeTestEnv+  let env' = env{ flags = (flags env){caching = 0} }+  result <- runHaxl env' caching3_+  assertEqual "result" result 18+  stats <- readIORef (statsRef env)+  assertEqual "rounds" 2 (numRounds stats)+  assertEqual "fetches" 4 (numFetches stats)+ exceptionTest1 = expectRounds 1 []   $ withDefault [] $ friendsOf 101 @@ -180,6 +190,7 @@   , TestLabel "caching2" $ TestCase caching2   , TestLabel "caching3" $ TestCase caching3   , TestLabel "CacheReuse" $ TestCase cacheReuse+  , TestLabel "NoCaching" $ TestCase noCaching   , TestLabel "exceptionTest1" $ TestCase exceptionTest1   , TestLabel "exceptionTest2" $ TestCase exceptionTest2   , TestLabel "deterministicExceptions" $ TestCase deterministicExceptions
tests/DataCacheTest.hs view
@@ -29,7 +29,7 @@   let cache =           DataCache.insert (Req 1 :: TestReq Int) m1 $           DataCache.insert (Req 2 :: TestReq String) m2 $-          DataCache.empty+          emptyDataCache    -- "Req 1" has a result of type Int, so if we try to look it up   -- with a result of type String, we should get Nothing, not a crash.
− tests/Main.hs
@@ -1,26 +0,0 @@-{-# LANGUAGE CPP, RebindableSyntax, OverloadedStrings #-}-module Main where--import TestExampleDataSource-import BatchTests-import CoreTests-import DataCacheTest-#ifdef HAVE_APPLICATIVEDO-import AdoTests-#endif--import Data.String-import Test.HUnit--import Haxl.Prelude--main :: IO Counts-main = runTestTT $ TestList-  [ TestLabel "ExampleDataSource" TestExampleDataSource.tests-  , TestLabel "BatchTests" BatchTests.tests-  , TestLabel "CoreTests" CoreTests.tests-  , TestLabel "DataCacheTests" DataCacheTest.tests-#ifdef HAVE_APPLICATIVEDO-  , TestLabel "AdoTests" AdoTests.tests-#endif-  ]
tests/MonadBench.hs view
@@ -5,9 +5,12 @@ -- found in the LICENSE file. An additional grant of patent rights can -- be found in the PATENTS file. +-- | Benchmarking tool for core performance characteristics of the Haxl monad.+ module MonadBench (main) where  import Control.Monad+import Data.List as List import Data.Time.Clock import System.Environment import System.Exit@@ -17,6 +20,8 @@ import Haxl.Prelude as Haxl import Prelude() +import Haxl.Core.Monad (newMemoWith, runMemo)+ import Haxl.Core  import ExampleDataSource@@ -47,8 +52,64 @@        foldl andThen (return []) (map listWombats [1.. fromIntegral n])        return ()     "tree" -> runHaxl env $ void $ tree n+    -- No memoization+    "memo0" -> runHaxl env $+      Haxl.sequence_ [unionWombats | _ <- [1..n]]+    -- One put, N gets.+    "memo1" -> runHaxl env $+      Haxl.sequence_ [memo (42 :: Int) unionWombats | _ <- [1..n]]+    -- N puts, N gets.+    "memo2" -> runHaxl env $+      Haxl.sequence_ [memo (i :: Int) unionWombats | i <- [1..n]]+    "memo3" ->+      runHaxl env $ do+        ref <- newMemoWith unionWombats+        let c = runMemo ref+        Haxl.sequence_ [c | _ <- [1..n]]+    "memo4" ->+      runHaxl env $ do+        let f = unionWombatsTo+        Haxl.sequence_ [f x | x <- take n $ cycle [100, 200 .. 1000]]+    "memo5" ->+      runHaxl env $ do+        f <- memoize1 unionWombatsTo+        Haxl.sequence_ [f x | x <- take n $ cycle [100, 200 .. 1000]]+    "memo6" ->+      runHaxl env $ do+        let f = unionWombatsFromTo+        Haxl.sequence_ [ f x y+                       | x <- take n $ cycle [100, 200 .. 1000]+                       , let y = x + 1000+                       ]+    "memo7" ->+      runHaxl env $ do+        f <- memoize2 unionWombatsFromTo+        Haxl.sequence_ [ f x y+                       | x <- take n $ cycle [100, 200 .. 1000]+                       , let y = x + 1000+                       ]++    "cc1" -> runHaxl env $+      Haxl.sequence_ [ cachedComputation (ListWombats 1000) unionWombats+                     | _ <- [1..n]+                     ]+     _ -> do-      hPutStrLn stderr "syntax: monadbench par1|par2|seqr|seql NUM"+      hPutStrLn stderr $ "syntax: monadbench " ++ concat+        [ "par1"+        , "par2"+        , "seqr"+        , "seql"+        , "memo0"+        , "memo1"+        , "memo2"+        , "memo3"+        , "memo4"+        , "memo5"+        , "memo6"+        , "memo7"+        , "cc1"+        ]       exitWith (ExitFailure 1)   t1 <- getCurrentTime   printf "%d reqs: %.2fs\n" n (realToFrac (t1 `diffUTCTime` t0) :: Double)@@ -62,3 +123,12 @@   [ tree (n-1)   , listWombats (fromIntegral n), tree (n-1)   ]++unionWombats :: GenHaxl () [Id]+unionWombats = foldl List.union [] <$> Haxl.mapM listWombats [1..1000]++unionWombatsTo :: Id -> GenHaxl () [Id]+unionWombatsTo x = foldl List.union [] <$> Haxl.mapM listWombats [1..x]++unionWombatsFromTo :: Id -> Id -> GenHaxl () [Id]+unionWombatsFromTo x y = foldl List.union [] <$> Haxl.mapM listWombats [x..y]
tests/TestExampleDataSource.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings, RebindableSyntax, MultiWayIf #-}+{-# LANGUAGE CPP, OverloadedStrings, RebindableSyntax, MultiWayIf #-} module TestExampleDataSource (tests) where  import Haxl.Prelude as Haxl@@ -11,6 +11,7 @@ import Test.HUnit import Data.IORef import Control.Exception+import System.FilePath  import ExampleDataSource import LoadCache@@ -173,5 +174,7 @@   env <- testEnv   runHaxl env loadCache   str <- runHaxl env dumpCacheAsHaskell-  loadcache <- readFile "sigma/haxl/core/tests/LoadCache.txt"-  assertEqual "dumpCacheAsHaskell" str loadcache+  loadcache <- readFile $ dropFileName __FILE__ </> "LoadCache.txt"+  -- The order of 'cacheRequest ...' calls is nondeterministic and+  -- differs among GHC versions, so we sort the lines for comparison.+  assertEqual "dumpCacheAsHaskell" (sort $ lines loadcache) (sort $ lines str)
+ tests/TestMain.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE CPP, OverloadedStrings #-}+module Main where++import Test.Framework (defaultMain)+import Test.Framework.Providers.HUnit (hUnitTestToTests)+import AllTests++main :: IO ()+main = defaultMain $ hUnitTestToTests allTests
tests/TestTypes.hs view
@@ -10,6 +10,7 @@    ) where  import Data.Aeson+import Data.Binary (Binary) import Data.Text (Text) import qualified Data.Text as Text import qualified Data.HashMap.Strict as HashMap@@ -36,7 +37,7 @@   newtype Id = Id Int-  deriving (Eq, Ord, Enum, Num, Integral, Real, Hashable, Typeable,+  deriving (Eq, Ord, Binary, Enum, Num, Integral, Real, Hashable, Typeable,             ToJSON, FromJSON)  instance Show Id where