This can be done safely if you allow yourself the opportunity to select a link for storing the card.
import Control.Monad.IO.Class memoM :: (Ord k, MonadIO m) => (k -> mv) -> m (k -> mv) | | | opportunity to allocate the map get to IO correctly
I am going to use MVar instead of IORef to get the most out of concurrency correctly. This is for correctness if it is used simultaneously, and not for performance. For performance, we can be more attractive than this and use double-check locks or a parallel card with a thinner lock.
import Control.Concurrent import Control.Monad.IO.Class import qualified Data.Map as Map memoM :: (Ord k, Monad m, MonadIO m) => (k -> mv) -> m (k -> mv) memoM once = do mapVar <- liftIO $ newMVar Map.empty return (\k -> inMVar mapVar (lookupInsertM once k)) -- like withMVar, but isn't exception safe inMVar :: (MonadIO m) => MVar a -> (a -> m (a, b)) -> mb inMVar mvar step = do (a, b) <- liftIO (takeMVar mvar) >>= step liftIO $ putMVar mvar a return b lookupInsertM :: (Ord k, Monad m) => (k -> mv) -> k -> Map.Map kv -> m (Map.Map kv, v) lookupInsertM once k map = case Map.lookup k map of Just v -> return (map, v) Nothing -> do v <- once k return (Map.insert kv map, v)
In fact, we do not use IO , we just bypass the state. Any monad should be able to do this with a transformer applied to it, so why are we kidding in IO ? This is because we want to be able to highlight these cards so memoM can be used for several functions. If we only care about one memoized effective function, we can simply use a state transformer.
{-# LANGUAGE GeneralizedNewtypeDeriving #-} import Control.Applicative import Control.Monad.Trans.Class import Control.Monad.Trans.State newtype MemoT kvma = MemoT {getMemoT :: StateT (k -> mv, Map.Map kv) ma} deriving (Functor, Applicative, Monad, MonadIO) instance MonadTrans (MemoT kv) where lift = MemoT . lift
This transformer adds the ability to search for values ββfrom a memoized effectful function
lookupMemoT :: (Ord k, Monad m) => k -> MemoT kvmv lookupMemoT k = MemoT . StateT $ \(once, map) -> do (map', v) <- lookupInsertM once k map return (v, (once, map'))
To run it and get in the main monad, we need to provide the efficient function that we want to memoize.
runMemoT :: (Monad m) => MemoT kvma -> (k -> mv) -> ma runMemoT memo once = evalStateT (getMemoT memo) (once, Map.empty)
Our MemoT uses Map for each function. Some features may be seen in a different way. The monad-memo package has an mtl- style class for monads that provide memoization for a specific function, and a more complex mechanism for building them, which does not necessarily use Map s.