SafeIO.hs 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. {-# LANGUAGE FlexibleContexts #-}
  2. {- |
  3. ==The problem:
  4. In Haskell it is usually simple to keep IOError bounded within a context,
  5. so that they can not interfere with the overall execution of a program. Since functional
  6. substitution is done in a hierarchical way, often a single try or catch statement is
  7. enough to treat all errors on an entire section of code.
  8. Interruptible monad transformers turn this realitty upside down. Since the entire point
  9. of this type class is separating the hierarchical code organization from the monadic
  10. contexts, it becomes usefull to keep the exceptions information at the monadic context,
  11. instead of the default of carrying it in the code hierarchy.
  12. That is, in the following example:
  13. @
  14. do
  15. let ct1 = createContext 1
  16. ct2 = createContext 2
  17. ct1' <- resume startClient ct1
  18. ct2' <- resume startClient ct2
  19. resume finishClient ct1'
  20. resume finishClient ct2'
  21. @
  22. It may be desirable to let any IO exception on @startClient@ with the @ct1@ context
  23. influence only the execution of @finishClient@ with the @ct1'@ context, and not
  24. affect any execution with the other contexts.
  25. SafeIO was created to enforce this kind of behavior.
  26. ==How to use:
  27. 1. Idealy, do not import lift, liftIO, or anything similar at your module.
  28. 2. Create an error type (let's call it @e@), and make it an instance of IOErrorDerivation.
  29. 3. Wrap your computation inside an @EitherT e@ transformer (and keep the EitherT the top-level
  30. transformer).
  31. 4. Use the safe functions on this module instead of lift, liftIO, liftBase, etc.
  32. Remember that the context of interruptible transformers are in the inverse order that the
  33. transformers appear on the stack, thus, at the end of execution if you want to retrieve
  34. the EitherT context, you'll have to peel all the other contexts from it first.
  35. -}
  36. module Control.Monad.Trans.SafeIO (
  37. IOErrorDerivation(..),
  38. safeIO,
  39. safeCT,
  40. hoistResult,
  41. liftSuccess
  42. )where
  43. import System.IO.Error
  44. import Control.Monad.IO.Class
  45. import Control.Monad.Trans.Class
  46. import Control.Monad.Trans.Either
  47. import Control.Monad.Trans.Control
  48. import qualified Control.Exception.Lifted as Lift
  49. {- |
  50. Class for types that keep IOError information. Instantiate this for an error type for
  51. using the safe functions from this module.
  52. -}
  53. class IOErrorDerivation e where
  54. -- | Transforms an IOError on another error type
  55. coerceIOError :: IOError -> e
  56. -- | Safe alternative to liftIO.
  57. safeIO :: (MonadIO m, IOErrorDerivation e) => IO a -> EitherT e m a
  58. safeIO io = (liftIO $ tryIOError io) >>= hoistResult
  59. -- | Safe alternative to lift for an stack that implements MonadBaseControl.
  60. safeCT :: (MonadBaseControl IO m, IOErrorDerivation e) => m a -> EitherT e m a
  61. safeCT f = (lift $ Lift.try f) >>= hoistResult
  62. -- | Specialized function that hoists a try into the monad.
  63. hoistResult :: (IOErrorDerivation e, Monad m) => Either IOError a -> EitherT e m a
  64. hoistResult (Left e) = left . coerceIOError $ e
  65. hoistResult (Right v) = right v
  66. {- |
  67. Lifts a function with error handling into a transformed function with
  68. the same kind of error handling.
  69. -}
  70. liftSuccess :: (IOErrorDerivation e, Monad m, MonadTrans t, Monad (t m)) =>
  71. EitherT e m a -> EitherT e (t m) a
  72. liftSuccess f = do
  73. r <- lift . lift . runEitherT $ f
  74. hoistEither r