packages feed

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

module RefExtras.EVar
  ( EVar
  , accessEVar
  , newEventualEVar
  , newReadyEVar
  , readEVar
  , tryReadEVar
  ) where

import Control.Monad (join)
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.IO.Unlift (MonadUnliftIO)
import UnliftIO.Exception (finally)
import UnliftIO.IORef (atomicWriteIORef, newIORef, readIORef)
import UnliftIO.MVar (MVar, modifyMVar, newEmptyMVar, newMVar, putMVar, readMVar, tryReadMVar)

-- | The /E/ in 'EVar' stands for /Eventual/.
-- It may or may not have a value, but once it does, it doesn't change.
newtype EVar a = EVar { unEVar :: MVar (Maybe a) }

-- | You can /access/ an 'EVar' by telling it how to compute the value!
-- If there is already a value, it simply returns it instead of computing it.
-- If the computation fails, the exception propagates, leaving the EVar empty.
-- Blocks on var and action.
accessEVar :: MonadUnliftIO m => EVar a -> m a -> m a
accessEVar (EVar w) act = modifyMVar w (fmap (\a -> (Just a, a)) . maybe act pure)

-- | Creates a new 'EVar' with a the given computation.
-- Note that this does not /return/ an 'EVar', but instead creates
-- and shares the 'EVar' before computing it so you can updates references
-- to it first in case of exceptions.
-- Blocks on action.
newEventualEVar :: MonadUnliftIO m => (EVar a -> m ()) -> m a -> m a
newEventualEVar share act = do
  w <- newEmptyMVar
  let e = EVar w
  share e
  -- Use IORef to ensure ONE put in the finally
  i <- newIORef Nothing
  flip finally (readIORef i >>= putMVar w) $ do
    a <- act
    atomicWriteIORef i (Just a)
    pure a

-- | Creates an 'EVar' with an already-computed value.
newReadyEVar :: MonadIO m => a -> m (EVar a)
newReadyEVar = fmap EVar . newMVar . Just

-- | Returns the value if the 'EVar' is computed. Non-blocking.
tryReadEVar :: MonadIO m => EVar a -> m (Maybe a)
tryReadEVar = fmap join . tryReadMVar . unEVar

-- | Returns the value of the 'EVar', blocking on pending computations.
readEVar :: MonadIO m => EVar a -> m (Maybe a)
readEVar = readMVar . unEVar