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