SafeIO.hs 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  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. Do not import Control.Monad.Trans, Control.Monad.IO 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.
  30. 4. Use the safe functions on this module instead of lift, liftIO, liftBase, etc.
  31. Remember that the context of interruptible transformers are in the inverse order that the
  32. transformers appear on the stack, thus, at the end of execution if you want to retrieve
  33. the EitherT context, you'll have to peel all the other contexts from it first.
  34. -}
  35. module Control.Monad.Trans.SafeIO (
  36. IOErrorDerivation(..),
  37. safeIO,
  38. safeCT
  39. )where
  40. import System.IO.Error
  41. import Control.Monad.IO.Class
  42. import Control.Monad.Trans.Class
  43. import Control.Monad.Trans.Either
  44. import Control.Monad.Trans.Control
  45. import qualified Control.Exception.Lifted as Lift
  46. {- |
  47. Class for types that keep IOError information. Instantiate this for an error type for
  48. using the safe functions from this module.
  49. -}
  50. class IOErrorDerivation e where
  51. -- | Transforms an IOError on another error type
  52. coerceIOError :: IOError -> e
  53. -- | Safe alternative to liftIO
  54. safeIO :: (MonadIO m, IOErrorDerivation e) => IO a -> EitherT e m a
  55. safeIO io = (liftIO $ tryIOError io) >>= hoistResult
  56. -- | Safe alternative to lift for an stack that implements MonadBaseControl.
  57. safeCT :: (MonadBaseControl IO m, IOErrorDerivation e) => m a -> EitherT e m a
  58. safeCT f = (lift $ Lift.try f) >>= hoistResult
  59. hoistResult :: (IOErrorDerivation e, Monad m) => Either IOError a -> EitherT e m a
  60. hoistResult (Left e) = left . coerceIOError $ e
  61. hoistResult (Right v) = right v