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 others
  • try: expose the error value (if computation fails) for usage in the do notation
  • withResource: 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: