Overview
MonadThrow
MonadThrow
is used to immediately stop bind
's sequential computation and return a value of its error type because of some unforeseeable error (e.g. error encountered when connecting to a database, file that was supposed to exist did not exist, etc).
It's default implmentation is ExceptT
:
-- e m a
newtype ExceptT error monad output =
ExceptT (monad (Either error output))
-- Pseudo-syntax: combines class and instancee together:
class (Monad m) => MonadThrow e (ExceptT e m) where
throwError :: forall a. e -> ExceptT e m a
throwError a = ExceptT (pure $ Left a)
ExceptT: Before and After
Before using ExceptT
, we would write this ugly verbose code:
getName :: Effect (Either Error String)
getAge :: Effect (Either Error Int)
main :: Effect Unit
main = do
eitherName <- getName
case eitherName of
Left error -> log $ "Error: " <> show error
Right name -> do
eitherName <- getAge
case maybeAge of
Left error -> log $ "Error: " <> show error
Right age -> do
log $ "Got name: " <> name <> " and age " <> show age
After using ExceptT
, we would write this clear readable code:
getName :: Effect (Either Error String)
getAge :: Effect (Either Error Int)
main :: Effect Unit
main = do
eitherResult <- runExceptT do
name <- ExceptT getName
age <- ExceptT getAge
pure { name, age }
case eitherResult of
Left error -> log $ "Error: " <> show error
Right rec -> do
log $ "Got name: " <> rec.name <> " and age " <> show rec.age
MonadError
MonadError
extends MonadThrow
by enabling a monad to catch the thrown error, attempt to handle it (by changing the error type to an output type), and then continue bind
's sequential computation. If catchError
can't handle the error, bind
's sequential computation will still stop at that point and return the value of the error type.
newtype ExceptT e m a = ExceptT (m (Either e a))
class (Monad m) => MonadError e (ExceptT e m) where
catchError :: forall a. ExceptT e m a -> (e -> ExceptT e m a) -> ExceptT e m a
catchError (ExceptT m) handleError =
ExceptT (m >>= (\either_E_or_A -> case either_E_or_A of
Left e -> case handleError e of ExceptT b -> b
Right a -> pure $ Right a))
For example,
getFileContents :: forall m.
MonadError m =>
String ->
m String
getFileContents pathToFile = do
readFileContents pathToFile `catchError` \fileNotFound ->
pure defaultValue
where
defaultValue = "foo"
Derived Functions
MonadThrow
does not have any derived functions.
MonadError
has 3 functions:
catchJust
: catch only the errors you want to try to handle and ignore the otherstry
: expose the error value (if computation fails) for usage in the do notationwithResource
: whether a computation fails or succeeds, clean up resources after it is done
Do Notation
Since MonadThrow/MonadError are error-related, we'll show the do notation in meta-language here since it will be harder to do so in the code examples:
-- MonadThrow
stopped <- throwError e
value1 <- otherComputation stopped
value2 <- otherComputation value1
-- MonadError
mightRun <- computationThatMayFail `catchError` computationWhenPreviousFailed
left_Error <- try computationThatFails
right_Output <- try computationThatSucceeds
output <- withResource getResource cleanup computationThatUsesResource
Laws, Instances, and Miscellaneous Functions
For its laws, see
For ExceptT
's instances, see
To handle/modify the output of an error computation: