Error Handling in Haskell
January 01, 0001The Kinds of Error Reports
- Exceptions
- Either
- ErrorT or EitherT
Parsing a File
- parseImage :: ByteString -> Either ParseError (Image PixelRGB8)
- readFile :: FilePath -> IO ByteString
- readImageFile fname = readFile fname »= return . parseImage :: FilePath -> IO (Either ParseError (Image PixelRGB8))
Exceptions
- throw :: Exception e => e -> a
- throwIO :: Exception e => e -> IO a
- catch :: Exception e => IO a -> (e -> IO a) -> IO a
- handle :: Exception e => (e -> IO a) -> IO a -> IO a
- try :: Exception e => IO a -> IO (Either e a)
- throwTo :: Exception e => ThreadId -> e -> IO ()
- error :: String -> a
Exceptions
\footnotesize
readFileExc :: FilePath -> IO ByteString
readFileExc = handle silenceENoEnt . readFile
where
silenceENoEnt :: IOException -> IO ByteString
silenceENoEnt exc | isDoesNotExistError exc = return empty
| otherwise = throw exc
readImageFileExc :: FilePath -> IO Image
readImageFileExc fn = do
bs <- readFileExc fn
either throw return (parseImage bs)
\normalsize
Either
\footnotesize
data ReadImageError = ParseError ParseError | ReadError IOException
readFileEither :: FilePath -> IO (Either IOException ByteString)
readFileEither fn = try (readFile fn) >>= return . either silenceENoEnt Right
where
silenceENoEnt :: IOException -> Either IOException ByteString
silenceENoEnt exc | isDoesNotExistError exc = Right empty
| otherwise = Left exc
readImageFileEither :: FilePath -> IO (Either ReadImageError Image)
readImageFileEither fn = do
mBs <- readFileEither fn
return $ case mBs of
Left err -> Left (ReadError err)
Right bs -> either (Left . ParseError) Right (parseImage bs)
\normalsize
ErrorT and EitherT
- newtype ErrorT e m a = ErrorT { runErrorT :: m (Either e a) }
- newtype EitherT e m a = EitherT { runEitherT :: m (Either e a) }
ErrorT and EitherT
- newtype ErrorT e m a = ErrorT { runErrorT :: m (Either e a) }
- newtype EitherT e m a = EitherT { runEitherT :: m (Either e a) }
- EitherT :: m (Either e a) -> EitherT e m a
- runEitherT :: EitherT e m a -> m (Either e a)
EitherT
- catch :: Exception e => IO a -> (e -> IO a) -> IO a
- catch :: Exception e => IO (Either e a) -> (e -> IO (Either e’ a)) -> IO (Either e’ a)
- catchT :: Monad m => EitherT e m a -> (e -> EitherT e’ m a) -> (EitherT e’ m a)
EitherT
- handle :: Exception e => (e -> IO a) -> IO a -> IO a
- handleT :: Monad m => (e -> EitherT e’ m a) -> EitherT e m a -> (EitherT e’ m a)
EitherT
- try :: Exception e => IO a -> IO (Either e a)
- tryIO :: MonadIO m => IO a -> EitherT IOException m a
- EitherT . try :: Exception e => IO a -> EitherT e IO a
EitherT
\footnotesize
catchT :: forall e e' m a. Monad m
=> EitherT e m a
-> (e -> EitherT e' m a)
-> EitherT e' m a
catchT action handler = EitherT $ do
res <- runEitherT action :: m (Either e a)
case res of
Right val -> return (Right val)
Left err -> runEitherT (handler err)
\normalsize
MonadError
\footnotesize
class Monad m => MonadError e m | m -> e where
throwError :: e -> m a
catchError :: m a -> (e -> m a) -> m a
\normalsize
Revisiting readImageFileEither
\footnotesize
data ReadImageError = ParseError ParseError | ReadError IOException
readImageFileEither :: FilePath -> IO (Either ReadImageError Image)
readImageFileEither fn = do
mBs <- readFileEither fn
return $ case mBs of
Left err -> Left (ReadError err)
Right bs -> either (Left . ParseError) Right (parseImage bs)
\normalsize
Revisiting readImageFileEither
\footnotesize
data ReadImageError = ParseError ParseError | ReadError IOException
readImageFileEither :: FilePath -> EitherT ReadImageError IO Image
readImageFileEither fn =
\normalsize
Revisiting readImageFileEither
\footnotesize
readImageFileEither' :: FilePath -> EitherT ReadImageError IO Image
readImageFileEither' fn = do
bs <- readFileEither' fn :: EitherT IOException IO ByteString
\normalsize
Revisiting readImageFileEither
\footnotesize
readImageFileEither' :: FilePath -> EitherT ReadImageError IO Image
readImageFileEither' fn = do
bs <- handleT (throwError . ReadError) (readFileEither' fn)
:: EitherT ReadImageError IO ByteString
\normalsize
Revisiting readImageFileEither
\footnotesize
readImageFileEither' :: FilePath -> EitherT ReadImageError IO Image
readImageFileEither' fn = do
bs <- handleT (throwError . ReadError) (readFileEither' fn)
case parseImage bs of
Right val -> return val
Left exc -> throwError (ParseError exc)
\normalsize
Revisiting readImageFileEither
\footnotesize
readImageFileEither' :: FilePath -> EitherT ReadImageError IO Image
readImageFileEither' fn = do
bs <- handleT (throwError . ReadError) (readFileEither' fn)
either (throwError . ParseError) return (parseImage bs)
\normalsize
Revisiting readImageFileEither
\footnotesize
readFileExc :: FilePath -> IO ByteString
readFileExc = handle silenceENoEnt . readFile
where
silenceENoEnt :: IOException -> IO ByteString
silenceENoEnt exc | isDoesNotExistError exc = return empty
| otherwise = throw exc
readFileEither' :: FilePath -> EitherT IOException IO ByteString
readFileEither' = handleT silenceENoEnt . tryIO . readFile
where
silenceENoEnt :: IOException -> EitherT IOException IO ByteString
silenceENoEnt exc | isDoesNotExistError exc = return empty
| otherwise = throwError exc
\normalsize
Revisiting readImageFileEither
\footnotesize
silenceENoEnt' :: (MonadError IOException m, MonadIO m)
=> IOException
-> m ByteString
silenceENoEnt' exc
| isDoesNotExistError exc = return empty
| otherwise = throwError exc
readFileExc :: FilePath -> IO ByteString
readFileExc = handle silenceENoEnt' . readFile
readFileEither' :: FilePath -> EitherT IOException IO ByteString
readFileEither' = handleT silenceENoEnt' . tryIO . readFile
\normalsize
Building an Application Stack
\footnotesize
data DiskStore = DiskStore { root :: FilePath }
newtype DiskStoreM a =
DSM {
uDSM :: ReaderT DiskStore
(EitherT DataStoreError IO)
a }
deriving ( Functor, Applicative, Monad, MonadIO,
, MonadReader DiskStore, MonadError DataStoreError )
\normalsize
Put an Object In
\footnotesize
putObject :: DataObject obj
=> Path
-> obj
-> Maybe ObjVersion
-> DiskStoreM ObjVersion
putObject path obj mVer = do
DiskStore{..} <- ask
let fsPath = root </> "objects" </> T.unpack (T.intercalate "/" elems)
createDirectoryIfMissing True (dropFileName fsPath)
BS.writeFile fsPath (content obj)
return (ObjVersion T.empty)
\normalsize
- createDirectoryIfMissing :: Bool -> FilePath -> IO ()
- writeFile :: FilePath -> ByteString -> IO ()
hoistIO
(IOException -> EitherT DataStoreError IO a) -> IO a -> DiskStoreM a
hoistIO
\footnotesize
hoistIO :: (IOException -> EitherT DataStoreError IO a)
-> IO a
-> EitherT IOException IO a
hoistIO handler action =
tryIO action
\normalsize
hoistIO
\footnotesize
hoistIO :: (IOException -> EitherT DataStoreError IO a)
-> IO a
-> EitherT DataStoreError IO a
hoistIO handler action =
handleT handler (tryIO action)
\normalsize
hoistIO
\footnotesize
hoistIO :: (IOException -> EitherT DataStoreError IO a)
-> IO a
-> ReaderT DiskStore (EitherT DataStoreError IO) a
hoistIO handler action =
ReaderT (\_ -> handleT handler
(tryIO action))
hoistIO handler action =
lift (handleT (throwError . handler)
(tryIO action))
\normalsize
hoistIO
\footnotesize
hoistIO :: (IOException -> EitherT DataStoreError IO a)
-> IO a
-> DiskStoreM a
hoistIO handler action =
DSM (lift (handleT handler (tryIO action)))
\normalsize
Back to adding the object
\footnotesize
putObject :: DataObject obj => Path
-> obj
-> Maybe ObjVersion
-> DiskStoreM ObjVersion
putObject (Path elems) obj mVer = do
DiskStore{..} <- ask
let fsPath = root </> "objects" </> T.unpack (T.intercalate "/" elems)
hoistIO (throwError . trIOExc) $ do
createDirectoryIfMissing True (dropFileName fsPath)
BS.writeFile fsPath (content obj)
return (ObjVersion T.empty)
\normalsize
Catching multiple types of Exceptions
This page left blank
Catching and handling inside the monad
\footnotesize
putObject :: DataObject obj => Path
-> obj
-> Maybe ObjVersion
-> DiskStoreM ObjVersion
putObject (Path elems) obj mVer = do
DiskStore{..} <- ask
let fsPath = root </> "objects" </> T.unpack (T.intercalate "/" elems)
res <- liftIO $ do
createDirectoryIfMissing True (dropFileName fsPath)
BS.writeFile fsPath (content obj)
return (ObjVersion T.empty)
\normalsize