{-# LANGUAGE DeriveDataTypeable , EmptyDataDecls , ExistentialQuantification , FlexibleInstances , IncoherentInstances , MultiParamTypeClasses , PolymorphicComponents , TypeSynonymInstances #-} ------------------------------------------------------------------------------- -- | -- Module : System.IOR.Internal -- Copyright : (c) Ivan Tomac 2008 -- License : BSD3 -- -- Maintainer : tomac `at` pacific `dot` net `dot` au -- Stability : experimental -- -- IOR monad internals. -- ------------------------------------------------------------------------------- module System.IOR.Internal ( RElem , RCons , RNil , IOR (..) , runIOR , newIOR , IORTag (..) , getIORTag , withIORTag , Resource (..) , Resource' (..) ) where import Control.Applicative import Control.Monad.Error import Control.Monad.Trans import Data.Generics import Data.IORef import System.IO.Error class RElem r rs data RCons r rs data RNil instance RElem r (RCons r rs) instance RElem r rs => RElem r (RCons r' rs) -- | 'IO' monad with support for region based resource allocation. -- A computation of type @'IOR' r rs a@ wraps an action of type @'IO' a@ -- where @r@ is an unconstrained type variable indicating the current region -- and @rs@ is a collection of all accessible regions within the computation. -- -- 'IO' actions can be lifted into the 'IOR' monad using 'liftIO'. -- It is safe to throw 'IOError'-s inside an 'IOR' computation. -- Allocated resources will be released on exit automatically. data IOR r rs a = IOR { unIOR :: IORTag r -> IO a } deriving (Data, Typeable) instance Monad (IOR r rs) where return = IOR . const . return m >> n = IOR $ liftA2 (>>) (unIOR m) (unIOR n) m >>= f = IOR $ liftA2 (>>=) (unIOR m) (flip $ unIOR . f) {-# INLINE return #-} {-# INLINE (>>) #-} {-# INLINE (>>=) #-} instance Functor (IOR r rs) where fmap f m = IOR $ fmap f . unIOR m {-# INLINE fmap #-} instance Applicative (IOR r rs) where pure = return (<*>) = ap instance MonadIO (IOR r rs) where liftIO = IOR . const {-# INLINE liftIO #-} instance MonadFix (IOR r rs) where mfix f = IOR $ mfix . flip (unIOR . f) {-# INLINE mfix #-} instance MonadError IOError (IOR r rs) where throwError = liftIO . throwError catchError m handler = IOR catchError' where catchError' = liftA2 catchError (unIOR m) handler' handler' = flip (unIOR . handler) run :: IOR r rs a -> IO a run m = do ref <- newIORef (0, []) e <- try $ unIOR m $ IORTag ref readIORef ref >>= mapM_ finalizeRes . snd either throwError return e where finalizeRes (Resource' r) = finalizer r -- | Create the initial region, @r@, and run the computation returning a -- value of type @'IO' a@. runIOR :: IOR r (RCons r RNil) a -> IO a runIOR = run -- | Create a new region @r'@ inside @r@. All resources allocated in -- @r'@ are only accessible from @r'@ and any of it's child regions. -- On exit from the region, all allocated resources are automatically -- released. newIOR :: IOR r' (RCons r' rs) a -> IOR r rs a newIOR = liftIO . run type IdTag = Integer -- | A region tag @'IORTag' r@ captures state of the region @r@ including all -- currently allocated resources in @r@. newtype IORTag r = IORTag { unIORTag :: IORef (IdTag, [Resource' r]) } deriving (Data, Eq, Typeable) -- | Get the current region's tag. getIORTag :: IOR r rs (IORTag r) getIORTag = IOR return -- | Temporarily change the current region from @r@ to @r'@. This allows -- allocation of resources in @r'@. -- -- @r'@ has to be one of the parent regions of @r@. withIORTag :: RElem r' rs => IORTag r' -> IOR r' rs a -> IOR r rs a withIORTag = (liftIO .) . flip unIOR -- | @'Resource' r a@ wraps a resource of type @a@ so it can be managed -- inside region @r@ and automatically released upon exit from @r@. data Resource r a = Resource { -- | Extract @a@ from the 'Resource' wrapper. getResource :: a -- | Tag that uniquely identifies the resource in the current region. , idTag :: IdTag -- | Reference to region state. , tagRef :: IORef (IdTag, [Resource' r]) -- | Finalizer for the resource. , finalizer :: IO () } deriving (Data, Typeable) instance Eq (Resource r a) where r1 == r2 = idTag r1 == idTag r2 data Resource' r = forall a. Resource' {-# UNPACK #-} !(Resource r a) deriving Typeable instance Eq (Resource' r) where Resource' r1 == Resource' r2 = idTag r1 == idTag r2