packages feed

ref-extras-0.2.0: src/RefExtras/Check.hs

module RefExtras.Check
  ( CheckEffect (..)
  , checkEffectRef
  , checkEffectXVar
  , runCheckEffectRef
  , runCheckEffectXVar
  ) where

import Control.Monad.IO.Class (MonadIO)
import Control.Monad.IO.Unlift (MonadUnliftIO)
import RefExtras.Classes (AtomicRef, atomicModifyRef, readRef)
import RefExtras.XVar (XVar, atomicModifyXVarM)

-- | Conditionally updates an AtomicRef. First reads the value with the prepare function, which
-- chooses to write with the commit function or chooses to return. This function allows you to
-- sequence a read, some effectful operation, and an optional write, all with the caveat that
-- the ref may have changed between the read and the write.
checkEffectRef :: (MonadIO m, AtomicRef r m) => r a -> (a -> m (Either x b)) -> (x -> a -> (a, b)) -> m b
checkEffectRef ref prepare commit = do
  a <- readRef ref
  e <- prepare a
  case e of
    Left b -> atomicModifyRef ref (commit b)
    Right c -> pure c

-- | checkEffectRef but locking around the effectful prepare function. You will block for the duration
-- of the prepare and commit functions but are guaranteed that the var does not change in the
-- meantime.
checkEffectXVar :: MonadUnliftIO m => XVar a -> (a -> m (Either x b)) -> (x -> a -> (a, b)) -> m b
checkEffectXVar ref prepare commit = atomicModifyXVarM ref $ \a -> do
  e <- prepare a
  case e of
    Left b -> pure (commit b a)
    Right c -> pure (a, c)

-- | The two prepare and commit functions packaged up.
data CheckEffect m a b where
  CheckEffect :: !(a -> m (Either x b)) -> !(x -> a -> (a, b)) -> CheckEffect m a b

runCheckEffectRef :: (MonadIO m, AtomicRef r m) => r a -> CheckEffect m a b -> m b
runCheckEffectRef ref (CheckEffect prepare commit) = checkEffectRef ref prepare commit

runCheckEffectXVar :: MonadUnliftIO m => XVar a -> CheckEffect m a b -> m b
runCheckEffectXVar ref (CheckEffect prepare commit) = checkEffectXVar ref prepare commit