如何在自定义Monad栈中使用Servant的throwError做错误处理?
问题:自定义Monad无法使用Servant的throwError
自定义的Monad定义如下:
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
想要使用Servant标准错误处理机制的throwError,写出如下代码:
foo :: App () foo = throwError err404
但代码无法编译,报错信息:
• 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
疑问:是否可为App推导该实例,或是否需要修改Monad栈?另外不想使用throw,因为会改变servant-client的行为。
解决方法
无法自动推导
MonadError ServerError App实例:当前的Monad栈(ReaderT + LoggingT + IO)里没有任何支持MonadError的转换器,deriving newtype只能基于栈中已有的实例进行推导,所以自动推导此实例行不通。修改Monad栈,加入
ExceptT ServerError:这是最直接的解决方案,调整栈结构,将ExceptT ServerError加入到IO层之上,这样就能通过deriving newtype自动获取MonadError实例。修改后的代码如下:
import Servant (ServerError) import Control.Monad.Except (ExceptT, MonadError) newtype App a = App { runApp :: ReaderT AppEnv (LoggingT (ExceptT ServerError IO)) a } deriving newtype ( Functor, Applicative, Monad, MonadIO, MonadReader AppEnv, MonadLogger, MonadError ServerError -- 现在可正常推导该实例 ) newtype AppEnv = AppEnv -- environment for the app
调整后即可直接使用throwError err404,完全符合Servant的错误处理规范,不会影响servant-client的行为。
- 基于Servant的
Handler重构Monad:如果不想手动调整栈结构,可以让App包裹Servant自带的Handler类型(Handler已经内置了MonadError ServerError实例),再叠加ReaderT和LoggingT。示例代码:
import Servant (Handler, ServerError) newtype App a = App { runApp :: ReaderT AppEnv (LoggingT Handler) a } deriving newtype ( Functor, Applicative, Monad, MonadIO, MonadReader AppEnv, MonadLogger, MonadError ServerError )
这种方式更贴合Servant生态,后续将App转换为Servant的Server类型时会更便捷。
内容的提问来源于stack exchange,提问作者l7r7
相关产品推荐
相关产品推荐

