-
-
Save shajra/0073dbfabbdb661cd8814cf96edbd5d4 to your computer and use it in GitHub Desktop.
| #!/usr/bin/env stack | |
| -- stack --install-ghc runghc --package safe-exceptions --package lens --package mtl | |
| {-# LANGUAGE TemplateHaskell #-} | |
| {-# LANGUAGE ConstraintKinds #-} | |
| {-# OPTIONS_GHC -Wall -Werror #-} | |
| import Control.Exception (IOException) | |
| import Control.Exception.Safe (MonadCatch, catch) | |
| import Control.Lens.TH (makeClassyPrisms) | |
| import Control.Monad.Error.Lens (throwing) | |
| import Control.Monad.Except (MonadError, runExceptT) | |
| import Control.Monad.Trans (MonadIO, liftIO) | |
| data FileFault = BadFile IOException | SomeOtherFileProblem deriving Show | |
| data AppFault = AppFileFault FileFault | SomeOtherAppProblem deriving Show | |
| makeClassyPrisms ''FileFault | |
| makeClassyPrisms ''AppFault | |
| instance AsFileFault AppFault where | |
| _FileFault = _AppFileFault . _FileFault | |
| type AppMonad e m = (MonadError e m, MonadCatch m, MonadIO m) | |
| work :: IO () | |
| work = putStr "read which file? " *> getLine >>= readFile >>= putStrLn | |
| handledWork :: (AsFileFault e, AppMonad e m) => m () | |
| handledWork = catch (liftIO work) (throwing _BadFile) | |
| handleFault :: AppFault -> IO () | |
| handleFault f = putStrLn "ERROR AT THE END OF WORLD" *> print f | |
| main :: IO () | |
| main = runExceptT handledWork >>= either handleFault pure |
| #!/usr/bin/env stack | |
| -- stack --install-ghc runghc --package safe-exceptions --package lens --package mtl | |
| {-# LANGUAGE FlexibleContexts #-} | |
| {-# OPTIONS_GHC -Wall -Werror #-} | |
| import Control.Exception (IOException) | |
| import Control.Exception.Safe (MonadCatch, catch) | |
| import Control.Monad.Except (MonadError, runExceptT, throwError) | |
| import Control.Monad.Trans (MonadIO, liftIO) | |
| data AppFault = BadFault IOException | SomeOtherAppProblem deriving Show | |
| work :: IO () | |
| work = putStr "read which file? " *> getLine >>= readFile >>= putStrLn | |
| handledWork :: (MonadError AppFault m, MonadCatch m, MonadIO m) => m () | |
| handledWork = catch (liftIO work) (throwError . BadFault) | |
| handleFault :: AppFault -> IO () | |
| handleFault f = putStrLn "ERROR AT THE END OF WORLD" *> print f | |
| main :: IO () | |
| main = runExceptT handledWork >>= either handleFault pure |
For a top level app monad, I'll usually use MonadThrow/Catch/Mask instead of MonadError. MonadError seems to work best when there's only a single possible exception type that'll actually work, but in a higher level app, you don't have that guarantee (especially if your MonadApp m is really just IO with niceties).
I also don't usually bother with prisms for the exceptions.
To clarify the above point re MonadError -- It's much nicer (to me) to have:
data FooException = FooException Text deriving (Eq, Show, Exception)
data BarException = BarException Text Int Int deriving (...)
-- ...rather than
data MyException = FooException Text | BarException Text Int Intsince these are really orthogonal concerns most of the time.
For anyone new to lenses or constraint kinds, I made "simple" version that stripped away those techniques.
@parsonsmatt's comment is one worth thinking deeply on. I'd use MonadThrow/Catch/Mask for exceptions that I really have no real intention of handling. But for everthing else, I'm really inclined to use MonadError. This is largely because I find reasoning throught total functions a large part of how I reason about correctness. I'm open to being shown the errors of my inclinations, though.
If you are building a library, can you accurately predict the instances in which you would throw an exception that nobody would want to catch? I've encountered a case in which a library did such a thing and it took some contortions to keep that exception from killing the application. And that meant that I couldn't process the data in pure code.
@savannidgerinel yeah, I feel the same way. Because it's so hard to pre-emptively know what's unhandleable in all contexts, a lot of stuff ends up in MonadError for me.
If you can imagine this were a larger program with larger with enough complexity to warrant a strategy like this, I'm interested in feedback on the approach -- especially ways to factor it better.