packages feed

GPipe-Core-0.2.3.1: src/Data/SNMap.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Data.SNMap
    ( SNMapReaderT
    , runSNMapReaderT
    , memoizeM
    , scopedM
    ) where

import           Control.Applicative              (Applicative)
import           Control.Monad.Exception          (MonadAsyncException,
                                                   MonadException)
import           Control.Monad.IO.Class           (MonadIO, liftIO)
import           Control.Monad.Trans.Class        (MonadTrans (..))
import           Control.Monad.Trans.State.Strict (StateT (StateT), evalStateT,
                                                   get, put)
import qualified Data.HashMap.Strict              as HT
import           System.Mem.StableName            (StableName, makeStableName)

{- A map (SN stands for stable name) to cache the results of computations
(m ends up being constrained to MonadIO m).
-}
newtype SNMap m a = SNMap { unSNMap :: HT.HashMap (StableName (m a)) a }

newSNMap :: SNMap m a
newSNMap = SNMap HT.empty

memoize :: MonadIO m
    => m (SNMap m a)        -- The "IO call" to retrieve our cache.
    -> (SNMap m a -> m ())  -- The "IO call" to store an updated cache.
    -> m a                  -- The "IO call" to execute and cache the result.
    -> m a                  -- The result being naturally also returned.
memoize getter putter m = do
    s <- liftIO $ makeStableName m
    x <- HT.lookup s . unSNMap <$> getter
    case x of
        Just a -> return a
        Nothing -> do
            a <- m
            -- Need to redo the getter action because of scopeM.
            getter >>= putter . SNMap . HT.insert s a . unSNMap
            return a

-- An (IO) action producing a 'b' value while caching 'a' values along the way.
newtype SNMapReaderT a m b = SNMapReaderT (StateT (SNMap (SNMapReaderT a m) a) m b)
    deriving (Functor, Applicative, Monad, MonadIO, MonadException, MonadAsyncException)

runSNMapReaderT :: MonadIO m => SNMapReaderT a m b -> m b
runSNMapReaderT (SNMapReaderT m) = do
    let h = newSNMap
    evalStateT m h

instance MonadTrans (SNMapReaderT a) where
    lift = SNMapReaderT . lift

-- Simplified memoize version when using a SNMapReaderT.
memoizeM :: MonadIO m => SNMapReaderT a m a -> SNMapReaderT a m a
memoizeM = memoize (SNMapReaderT get) (SNMapReaderT . put)

-- | Run a subcomputation in a scope, where nothing memoized inside will be remembered after
scopedM :: MonadIO m => SNMapReaderT a m x -> SNMapReaderT a m x
scopedM m = do
    save <- SNMapReaderT get
    x <- m
    SNMapReaderT $ put save
    return x