Skip to content

Instantly share code, notes, and snippets.

@shajra
Last active September 25, 2018 14:22
Show Gist options
  • Select an option

  • Save shajra/0073dbfabbdb661cd8814cf96edbd5d4 to your computer and use it in GitHub Desktop.

Select an option

Save shajra/0073dbfabbdb661cd8814cf96edbd5d4 to your computer and use it in GitHub Desktop.
Real-world exception handling in Haskell that seems like overkill for this simple example
#!/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
@shajra

shajra commented Aug 29, 2016

Copy link
Copy Markdown
Author

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.

@parsonsmatt

Copy link
Copy Markdown

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.

@parsonsmatt

Copy link
Copy Markdown

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 Int

since these are really orthogonal concerns most of the time.

@shajra

shajra commented Aug 29, 2016

Copy link
Copy Markdown
Author

For anyone new to lenses or constraint kinds, I made "simple" version that stripped away those techniques.

@shajra

shajra commented Aug 29, 2016

Copy link
Copy Markdown
Author

@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.

@savannidgerinel

Copy link
Copy Markdown

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.

@shajra

shajra commented Aug 29, 2016

Copy link
Copy Markdown
Author

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment