GPipe-Core-0.2.3.0: src/Data/SNMap.hs
-- {-# LANGUAGE GeneralizedNewtypeDeriving, ExistentialQuantification, RankNTypes #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Data.SNMap (
-- SNMap,
SNMapReaderT,
runSNMapReaderT,
-- newSNMap,
-- memoize,
memoizeM,
scopedM
)where
import System.Mem.StableName
import qualified Data.HashTable.IO as HT
import Data.Functor
import Control.Monad.IO.Class (liftIO, MonadIO)
import Control.Monad.Trans.Class
import System.Mem.Weak (addFinalizer)
import Control.Applicative (Applicative)
import Control.Monad.Exception (MonadException, MonadAsyncException)
import Control.Monad.Trans.State.Strict
{- 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 (HT.BasicHashTable (StableName (m a)) a)
newSNMap :: IO (SNMap m a)
newSNMap = SNMap <$> HT.new
memoize :: MonadIO m
=> m (SNMap m a) -- An "IO call" to retrieve our cache.
-> m a -- The "IO call" to execute and cache the result.
-> m a -- The result being naturally also returned.
memoize getter m = do s <- liftIO $ makeStableName $! m -- Does forcing the evaluation make sense here (since we try to avoid it...)? Is it just the first level?
(SNMap h) <- getter
x <- liftIO $ HT.lookup h s
case x of
Just a -> return a
Nothing -> do a <- m
(SNMap h') <- getter --Need to redo because of scope (TODO Check it)
liftIO $ HT.insert h' s a
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 h <- liftIO 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)
-- | 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 SNMap h <- SNMapReaderT get
save <- liftIO $ HT.toList h
x <- m
h' <- liftIO $ HT.fromList save
SNMapReaderT $ put (SNMap h')
return x