given a monad like this:
newtype App a = App
{ runApp :: ReaderT AppEnv (LoggingT IO) a
}
deriving newtype
( Functor,
Applicative,
Monad,
MonadIO,
MonadReader AppEnv,
MonadLogger
)
newtype AppEnv = AppEnv -- environment for the app
I'd like to use the standard error handling from Servant using throwError
:
foo :: App ()
foo = throwError err404
which doesn't compile
• No instance for (Control.Monad.Error.Class.MonadError
ServerError App)
arising from a use of ‘throwError’
• In the expression: throwError err404
In an equation for ‘foo’: foo = throwError err404
and I can't find a way to make that work. Can I derive such an instance for App
? Do I need to change the monad stack?
I could use throw
, but that does change the behavior of servant-client
, which I want to avoid.
If we want to throwError
values of type ServerError
, our monad needs to be an instance of MonadError ServerError
. Looking at the available instances, we can get an idea of what we would need to add to our monad stack.
These instances won't work because they are for specific error types different from ServerError
:
MonadError IOException IO
MonadError () Maybe
This instance would force us to use Either
as our base monad:
MonadError e (Either e)
Then there are a whole bunch of "passthrough" instances that propagate the MonadError
constraint from the base monad, but don't introduce it:
MonadError e m => MonadError e (IdentityT m)
MonadError e m => MonadError e (ReaderT r m)
MonadError e m => MonadError e (StateT s m)
...
This is the instance we need:
Monad m => MonadError e (ExceptT e m)
We need to add an ExceptT ServerError
transformer somewhere in our monad stack, and newtype derive MonadError ServerError
.
An annoyance with this solution is that introducing ExceptT
might make operations like catch
(for runtime exceptions) more difficult to perform.