MonadError vs MonadThrow/MonadCatch

Viewed 150

I am currently trying to understand which exception mechanism should be used in Haskell application these days. For example I want to build a stack based on StateT monad transformer.

With MonadError the solution looks like this:

data StackError = EmptyStack | SingletonStack
  deriving (Show)
type Stack m a = (MonadError StackError m, MonadState [a] m)

push :: Stack m a => a -> m ()
push = modify . (:)

pop :: Stack m a => m a
pop = do
  xs <- get
  when (null xs) $ throwError EmptyStack
  modify tail
  pure $ head xs

runOpTop :: Stack m a => (a -> a -> a) -> m ()
runOpTop f = do
  a <- pop
  b <- pop `catchError` (const $ push a >> throwError SingletonStack)
  push $ f a b

runStack :: StateT s (Either e) a -> s -> Either e (a, s)
runStack = runStateT

With MonadThrow/MonadCatch the solution looks like this:

data StackError = EmptyStack | SingletonStack
  deriving (Show, Exception)
type Stack m a = (MonadThrow m, MonadCatch m, MonadState [a] m)

push :: Stack m a => a -> m ()
push = modify . (:)

pop :: Stack m a => m a
pop = do
  xs <- get
  when (null xs) $ throwM EmptyStack
  modify tail
  pure $ head xs

runOpTop :: Stack m a => (a -> a -> a) -> m ()
runOpTop f = do
  a <- pop
  b <- pop `catch` (const @_ @SomeException $ push a >> throwM SingletonStack)
  push $ f a b

runStack :: StateT s (Either e) a -> s -> Either e (a, s)
runStack = runStateT

runStackIO :: StateT s IO a -> s -> IO (a, s)
runStackIO = runStateT

The question is: What solution is the preferrable one? When to use MonadError and when to use MonadThrow/MonadCatch?

0 Answers
Related