-- |
-- Stability   :  Ultra-Violence
-- Portability :  I'm too young to die
-- Provides one with the ability to pass her own monads in the callbacks.
{-# LANGUAGE TypeFamilies, EmptyDataDecls #-}
module Control.Monad.EmbedIO
	( EmbedIO(..)
	, Void
	, bracketE
	, catchE
	, handleE
	, tryE
	, throwE
	, forkE
	) where

import Control.Concurrent
import Control.Exception
import Control.Monad.IO.Class
import Prelude hiding (catch)

-- |'MonadIO's that can be collapsed to and restored from a distinct value.
class (MonadIO o) => EmbedIO o where
	-- |Intermediate state storage type.
	type Content o
	-- |Propagate an 'IO' operation over the storage type to the monadic type.
	embed :: (Content o -> IO a) -> o a
	-- |Run the monadic computation using supplied state.
	callback :: o a -> Content o -> IO a

-- |Empty type. Used to represent state for 'IO' monad.
data Void

instance EmbedIO IO where
	type Content IO = Void
	embed f = f undefined
	callback action _ = action

-- |'bracket' equivalent.
bracketE :: EmbedIO m => m r -> (r -> m b) -> (r -> m a) -> m a
bracketE before after during =
	embed $ \x -> bracket (before' x) (\a -> after' a x) (\a -> during' a x)
	where
	before' x = callback before x
	after' a x = callback (after a) x
	during' a x = callback (during a) x

-- |'catch' equivalent.
catchE :: (EmbedIO m, Exception e) => m a -> (e -> m a) -> m a
catchE action handler = embed $ \x -> catch (action' x) (\e -> handler' e x)
	where
	action' x = callback action x
	handler' e x = callback (handler e) x

-- |'handle' equivalent.
handleE :: (EmbedIO m, Exception e) => (e -> m a) -> m a -> m a
handleE = flip catchE

-- |'try' equivalent.
tryE :: (EmbedIO m, Exception e) => m a -> m (Either e a)
tryE action = embed $ \x -> try (callback action x)

-- |'throw' equivalent.
throwE :: (EmbedIO m, Exception e) => e -> m a
throwE = liftIO . throwIO

-- |'forkIO' equivalent.
forkE :: EmbedIO m => m () -> m ThreadId
forkE action = embed $ \x -> forkIO (callback action x)