packages feed

ref-extras-0.1.1: src/RefExtras/Memo.hs

module RefExtras.Memo
  ( Memo
  , accessMemo
  , clearMemo
  , freezeMemo
  , newMemo
  , thawMemo
  , tryReadMemo
  , writeMemo
  ) where

import Control.Monad (join)
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.IO.Unlift (MonadUnliftIO)
import Data.Hashable (Hashable)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Prelude
import RefExtras.EVar (EVar, accessEVar, newEventualEVar, newReadyEVar, tryReadEVar, writeEVar)
import RefExtras.XVar (XVar, newXVar, readXVar, splitXVar, writeXVar)

newtype Memo k a = Memo { unMemo :: XVar (HashMap k (EVar a)) }

newMemo :: MonadIO m => m (Memo k a)
newMemo = fmap Memo (newXVar HashMap.empty)

clearMemo :: MonadIO m => Memo k a -> m ()
clearMemo (Memo v) = writeXVar v HashMap.empty

accessMemo :: (MonadUnliftIO m, Eq k, Hashable k) => Memo k a -> k -> m a -> m a
accessMemo (Memo v) k act = splitXVar v $ \m write ->
  case HashMap.lookup k m of
    Just w -> write m >> accessEVar w act
    Nothing -> newEventualEVar (\w -> write (HashMap.insert k w m)) act

tryReadMemo :: (MonadIO m, Eq k, Hashable k) => Memo k a -> k -> m (Maybe a)
tryReadMemo (Memo v) k = do
  m <- readXVar v
  case HashMap.lookup k m of
    Nothing -> pure Nothing
    Just w -> tryReadEVar w

-- Blocks if k is being populated
writeMemo :: (MonadUnliftIO m, Eq k, Hashable k) => Memo k a -> k -> a -> m ()
writeMemo (Memo v) k a = splitXVar v $ \m write ->
  case HashMap.lookup k m of
    Just w -> write m >> writeEVar w a
    Nothing -> do
      w <- newReadyEVar a
      write (HashMap.insert k w m)

bindFor :: (Monad t, Traversable t, Applicative f) => t a -> (a -> f (t b)) -> f (t b)
bindFor t f = fmap join (traverse f t)

freezeMemo :: (MonadIO m, Eq k, Hashable k) => Memo k a -> m (HashMap k a)
freezeMemo (Memo v) = do
  m <- readXVar v
  fmap HashMap.fromList $ bindFor (HashMap.toList m) $ \(k, w) -> do
    ma <- tryReadEVar w
    case ma of
      Just a -> pure [(k, a)]
      _ -> pure []

thawMemo :: MonadIO m => HashMap k a -> m (Memo k a)
thawMemo m = do
  n <- traverse newReadyEVar m
  v <- newXVar n
  pure (Memo v)