{-----------------------------------------------------------------------------
    reactive-banana
------------------------------------------------------------------------------}
{-# LANGUAGE RecursiveDo #-}
module Reactive.Banana.Internal.Cached (
    -- | Utility for executing monadic actions once
    -- and then retrieving values from a cache.
    -- 
    -- Very useful for observable sharing.
    HasVault(..),
    Cached, runCached, mkCached, fromPure,
    liftCached1, liftCached2,
    ) where

import Control.Monad
import Control.Monad.Fix
import Data.Unique.Really
import qualified Data.Vault as Vault
import System.IO.Unsafe

{-----------------------------------------------------------------------------
    Cache type
------------------------------------------------------------------------------}
data Cached m a = Cached (m a)

runCached :: Cached m a -> m a
runCached (Cached x) = x

-- | Type class for monads that have a 'Vault' that can be used.
class (Monad m, MonadFix m) => HasVault m where
    retrieve :: Vault.Key a -> m (Maybe a)
    write    :: Vault.Key a -> a -> m ()

-- | An action whose result will be cached.
-- Executing the action the first time in the monad will
-- execute the side effects. From then on,
-- only the generated value will be returned.
{-# NOINLINE mkCached #-}
mkCached :: HasVault m => m a -> Cached m a
mkCached m = unsafePerformIO $ do
    key <- Vault.newKey
    return $ Cached $ do
        ma <- retrieve key      -- look up calculation result
        case ma of
            Nothing -> mdo
                write key a     -- black-hole result first
                a <- m          -- evaluate
                return a
            Just a  -> return a -- return cached result

-- | Return a pure value.
-- Doesn't make use of the cache 'Vault'.
fromPure :: HasVault m => a -> Cached m a
fromPure = Cached . return

liftCached1
    :: HasVault m
    => (a -> m b)
    -> Cached m a -> Cached m b
liftCached1 f ca = mkCached $ do
    a <- runCached ca
    f a

liftCached2
    :: HasVault m
    => (a -> b -> m c)
    -> Cached m a -> Cached m b -> Cached m c
liftCached2 f ca cb = mkCached $ do
    a <- runCached ca
    b <- runCached cb
    f a b