system-lifted-0.2.0.1: src/System/Lifted.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE QuasiQuotes #-}
module System.Lifted (
HandlerList (..)
, IOT (..)
, deriveSystemLiftedErrors
, IOExceptionHandling (..)
, joinMaybeMT
, joinMMT
, joinMaybeET
, joinMET
, joinMaybeErrT
, joinMErrT
, joinEitherET
, joinEET
, errorTtoEitherT
, evalEither
, evalEitherT
, reportEither
, reportEitherT
{-, showStr-}
{-, tshow-}
) where
import Data.String
import Data.Text (Text)
import qualified Data.Text as T
import Data.Typeable
import Data.Functor.Identity
import Control.Monad (join)
import Control.Monad.IO.Class
import Control.Monad.Trans.Identity
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Either
import Control.Monad.Trans.Error
import GHC.IO.Exception
import System.IO.Error
import System.Exit
import Control.Exception
import Language.Haskell.TH
import Language.Haskell.Meta.Parse
-- | Convert an Either value to a Maybe value
--
-- This function is provided with a different name convention on
-- @Data.Either.Combinators@:
--
-- @
-- 'eitherToMaybe' = 'rightToMaybe'
-- @
--
eitherToMaybe :: Either a b -> Maybe b
eitherToMaybe = either (const Nothing) Just
data IOExceptionHandling = AllowIOE [IOErrorType]
| DisallowIOE [IOErrorType]
| AllIOE
deriving (Eq,Typeable)
instance Show IOExceptionHandling where
show (AllowIOE _) = "AllIOE"
show (DisallowIOE _) = "DisallowIOE"
show AllIOE = "AllIOE"
processIOExcepts :: IOExceptionHandling -> IOException -> IOException
processIOExcepts ioeh ioe =
case ioeh of
-- | Provided a list of IOExceptions to allow (reported as Left or Nothing),
-- throw a IOException if not in this list (White Listing).
AllowIOE lst -> if ioeGetErrorType ioe `elem` lst
then ioe
else throw ioe
-- | Provided a list of IOExceptions to forbid, rethrow a IOException if
-- mentioned in this list (Black Listing).
DisallowIOE lst -> if ioeGetErrorType ioe `elem` lst
then throw ioe
else ioe
-- | No exception filtering done, all reported as Left or Nothing
AllIOE -> ioe
-- | Do not rethrow any IO Exception
{-allowIOExcepts :: [IOErrorType] -> IOException -> IOException-}
{-allowIOExcepts _ ioe = ioe-}
class HandlerList errs a where
handlerList :: errs -> [Handler a]
instance HandlerList (IOException -> IOException) String where
handlerList f = [Handler (\(e::IOException) -> return . show . f $ e)]
instance HandlerList (IOException -> IOException) () where
handlerList f = [Handler (\(e::IOException) -> evaluate (f e) >> return ())]
instance HandlerList (IOException -> IOException) Text where
handlerList f = [Handler (\(e::IOException) -> return . T.pack . show . f $ e)]
instance HandlerList (IOException -> IOException) IOException where
handlerList f = [Handler (\(e::IOException) -> return . f $ e)]
-- One possibility is to define it for MaybeT, and leave it open
-- to EitherT. Does it make sense to define it also for ListT?
-- But then [] would mean failure, wouldn't it?
-- [()] success?
{-handlerListIoUnit :: IOExceptionHandling -> [Handler ()]-}
{-handlerListIoUnit f = handlerList (processIOExcepts f)-}
class Tries a b c | c -> a b where
tries :: [Handler a] -> IO b -> IO c
instance Tries () b (Identity b) where
tries _ io = fmap Identity io
instance Tries a b (Either a b) where
tries handlers io = fmap Right io
`catch` catchesHandler handlers
instance Tries () b (Maybe b) where
tries handlers io = fmap Just io
`catch` (fmap eitherToMaybe . catchesHandler handlers)
catchesHandler :: [Handler a] -> SomeException -> IO (Either a b)
catchesHandler handlers e = foldr tryHandler (throw e) handlers
where tryHandler (Handler handler) res =
case fromException e of
Just e' -> fmap Left (handler e')
Nothing -> res
class ToT n t m a | t -> n where
toT :: m (n a) -> t m a
instance ToT Maybe MaybeT IO a where
toT = MaybeT
instance ToT (Either b) (EitherT b) IO a where
toT = EitherT
instance ToT Identity IdentityT IO a where
toT = IdentityT . fmap runIdentity
instance ToT (Either b) (ErrorT b) IO a where
toT = ErrorT
class IOT t m a where
ioT :: m a -> t m a
ioFilterT :: IOExceptionHandling -> m a -> t m a
evalTHStr :: String -> Q Exp
evalTHStr = return . either (\_ -> error "Error in template haskell") id
. parseExp
-- | The io handlers is passed as string due to TH peculiarities (and
-- my lack of TH knoledge, mainly).
deriveSystemLiftedErrors :: String -> Name -> DecsQ
deriveSystemLiftedErrors ioh tp = let
nm = conT tp
iohv = evalTHStr ioh
in [d|
instance IOT $nm IO a where
ioT iof = toT $ tries (handlerList (processIOExcepts $iohv)) iof
ioFilterT ioeh iof = toT $ tries (handlerList (processIOExcepts ioeh)) iof
|]
joinMaybeMT, joinMMT :: MaybeT IO (Maybe a) -> MaybeT IO a
joinMMT = joinMaybeMT
joinMaybeMT mbt = do
mb <- liftIO $ runMaybeT mbt
MaybeT . return $ maybe Nothing (maybe Nothing Just) mb
joinMaybeET, joinMET :: b -> EitherT b IO (Maybe a) -> EitherT b IO a
joinMET = joinMaybeET
joinMaybeET e mbt = do
mb <- liftIO $ runEitherT mbt
hoistEither $ either Left (maybe (Left e) Right) mb
joinEitherET, joinEET :: EitherT b IO (Either b a) -> EitherT b IO a
joinEET = joinEitherET
joinEitherET eit = hoistEither . join =<< (liftIO . runEitherT $ eit)
joinMaybeErrT, joinMErrT :: (Error b)
=> b -> ErrorT b IO (Maybe a) -> ErrorT b IO a
joinMErrT = joinMaybeErrT
joinMaybeErrT e mbt = do
mb <- liftIO $ runErrorT mbt
ErrorT . return $ either Left (maybe (Left e) Right) mb
errorTtoEitherT :: ErrorT a IO b -> EitherT a IO b
errorTtoEitherT = EitherT . runErrorT
-- | Evaluate an @Either IOException a@ to either raise the exception
-- or return the right value
evalEither :: Either IOException a -> IO a
evalEither ei = case ei of
Left e -> throwIO e
Right p -> return p
-- | Evaluate an @EitherT IOException IO a@ to either raise the exception
-- or return the right value
evalEitherT :: EitherT IOException IO a -> IO a
evalEitherT eit = runEitherT eit >>= evalEither
-- | Show a string like value (@String@ or @Text@) without the annoying quotes
showStr :: (IsString s, Show s) => s -> String
showStr = Prelude.init . Prelude.tail . show
-- | Show as text
{-tshow :: (Show s) => s -> Text-}
{-tshow = T.pack . show-}
-- | Either return the right value or print the error message in the @Left@ value
-- and exit
reportEither :: (IsString e, Show e) => Either e a -> IO a
reportEither ei = case ei of
Left e -> (putStrLn . showStr $ e) >> exitFailure
Right p -> return p
-- | Either return the right value or print the error message in the @Left@ value
-- and exit
reportEitherT :: (IsString e, Show e) => EitherT e IO a -> IO a
reportEitherT eit = runEitherT eit >>= reportEither