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?