{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | Memoize the results of actions. In other words: actions -- will be run once, on demand, and their results saved. -- -- Exceptions semantics: if a synchronous exception is thrown while performing -- the computation, that result will be saved and rethrown each time -- 'runMemoized' is called subsequently.' -- -- @since 0.2.8.0 module UnliftIO.Memoize ( Memoized , runMemoized , memoizeRef , memoizeMVar ) where import Control.Applicative as A import Control.Monad (join) import Control.Monad.IO.Unlift import UnliftIO.Exception import UnliftIO.IORef import UnliftIO.MVar -- | A \"run once\" value, with results saved. Extract the value with -- 'runMemoized'. For single-threaded usage, you can use 'memoizeRef' to -- create a value. If you need guarantees that only one thread will run the -- action at a time, use 'memoizeMVar'. -- -- Note that this type provides a 'Show' instance for convenience, but not -- useful information can be provided. -- -- @since 0.2.8.0 newtype Memoized a = Memoized (IO a) deriving (Functor, A.Applicative, Monad) instance Show (Memoized a) where show _ = "<<Memoized>>" -- | Extract a value from a 'Memoized', running an action if no cached value is -- available. -- -- @since 0.2.8.0 runMemoized :: MonadIO m => Memoized a -> m a runMemoized (Memoized m) = liftIO m {-# INLINE runMemoized #-} -- | Create a new 'Memoized' value using an 'IORef' under the surface. Note that -- the action may be run in multiple threads simultaneously, so this may not be -- thread safe (depending on the underlying action). Consider using -- 'memoizeMVar'. -- -- @since 0.2.8.0 memoizeRef :: MonadUnliftIO m => m a -> m (Memoized a) memoizeRef action = withRunInIO $ \run -> do ref <- newIORef Nothing pure $ Memoized $ do mres <- readIORef ref res <- case mres of Just res -> pure res Nothing -> do res <- tryAny $ run action writeIORef ref $ Just res pure res either throwIO pure res -- | Same as 'memoizeRef', but uses an 'MVar' to ensure that an action is -- only run once, even in a multithreaded application. -- -- @since 0.2.8.0 memoizeMVar :: MonadUnliftIO m => m a -> m (Memoized a) memoizeMVar action = withRunInIO $ \run -> do var <- newMVar Nothing pure $ Memoized $ join $ modifyMVar var $ \mres -> do res <- maybe (tryAny $ run action) pure mres pure (Just res, either throwIO pure res)