packages feed

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

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

import Control.Monad (join, void)
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, swapMVar, tryReadMVar)

newtype EVar a = EVar { unEVar :: MVar (Maybe a) }

-- 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)

-- 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

-- Non-blocking
newReadyEVar :: MonadIO m => a -> m (EVar a)
newReadyEVar = fmap EVar . newMVar . Just

-- Non-blocking
tryReadEVar :: MonadIO m => EVar a -> m (Maybe a)
tryReadEVar = fmap join . tryReadMVar . unEVar

-- Blocks on var
readEVar :: MonadIO m => EVar a -> m (Maybe a)
readEVar = readMVar . unEVar

-- Blocks on var
writeEVar :: MonadIO m => EVar a -> a -> m ()
writeEVar (EVar w) = void . swapMVar w . Just

-- Blocks on var and action
modifyEVar :: MonadUnliftIO m => EVar a -> m a -> (a -> (a, b)) -> m b
modifyEVar (EVar w) act f = modifyMVar w (fmap (\a -> let (a', b) = f a in (Just a', b)) . maybe act pure)