-- | Memoize IO actions, performing them at most once,
-- but recalling their result for subsequent invocations.
-- This library provides two sequencing strategies:
-- lazy ('once'), and concurrent ('eagerlyOnce').
-- 
-- The following property holds: @join . once === id@.
-- The same is true for @eagerlyOnce@.
-- 
-- The memory allocated for a memoized result will obviously not
-- be available for garbage collection until the corresponding
-- memoized action is also available for garbage collection.
module System.IO.Memoize (
    once
  , eagerlyOnce
  , ioMemo
  , ioMemo'
  , ioMemoPar
  ) where

import Control.Concurrent.Async (async, wait)
import Control.Concurrent.Cache (newCache, fetch)

-- | Memoize an IO action. The action will be performed
-- the first time that it its value is demanded;
-- all subsequent invocations will simply recall the value acquired
-- from the first call. If the value is never demanded,
-- then the action will never be performed.
-- 
-- This is basically a safe version of 'System.IO.Unsafe.unsafeInterleaveIO'.
-- Exceptions will be propagated to the caller, and the action will be retried
-- at each invocation, only until it has successfully completed once.
-- 
-- Example usage:
-- 
-- >>> getLine' <- once getLine
-- 
-- >>> replicateM 3 getLine'
-- Hello
-- ["Hello", "Hello", "Hello"]
once :: IO a -> IO (IO a)
once action = do
  cache <- newCache
  return (fetch cache action)

-- | Memoize an IO action.
-- The action will be started immediately in a new thread.
-- Attempts to access the result will block until the action is finished.
-- If the action produces an error, then attempts to access its value
-- will re-raise the same error each time.
eagerlyOnce :: IO a -> IO (IO a)
eagerlyOnce action = do
  thread <- async action
  return (wait thread)


{-# DEPRECATED ioMemo' "Please just call the action directly." #-}
-- | Memoize an IO action.
-- The action will be performed immediately;
-- all subsequent invocations
-- will recall the value acquired.
ioMemo' :: IO a -> IO (IO a)
ioMemo' action = do
  v <- action
  return (return v)

{-# DEPRECATED ioMemo "Please use 'once'." #-}
ioMemo :: IO a -> IO (IO a)
ioMemo = once

{-# DEPRECATED ioMemoPar "Please use 'eagerlyOnce'." #-}
ioMemoPar :: IO a -> IO (IO a)
ioMemoPar = eagerlyOnce