【问题标题】:Either computations in servant handler仆人处理程序中的任一计算
【发布时间】:2018-03-28 06:43:22
【问题描述】:

servant-server HandlerExceptT 上的新类型包装器,具有 MonadThrowMonadCatchMonadError 等的实例。

这可能是一个有点做作的例子,但它显示了我经常遇到的一个问题:

在处理程序中,我想调用三个返回 Either String Int 的函数,然后执行 Int -> Int -> Int -> IO (Either SomeError Text) 类型的计算,从之前获取三个 Ints。

我应该如何构造这段代码以确保尽早返回错误?

我发现我可以使用EitherMonad 实例将前三个Either String Int 计算“折叠”成例如Either String (Int,Int,Int),然后将IO计算绑定到某个结果值,然后使用case来决定是返回成功的结果还是使用throwError抛出SomeError类型(转换后?),但是我希望能够做到以下几点:

f, g, h :: Either String Int
a :: Int -> Int -> Int -> IO (Either SomeError Text) 

myHandler :: Handler Text
myHandler = do
    x1 <- f
    x2 <- g
    x3 <- h
    liftIO $ convertError $ (a x1 x2 x3)

是否可以写成类似上面的代码?

【问题讨论】:

  • 您可能会遇到HandlerExceptT ServantErr 的问题,但您的错误是Strings。我们可以假设你有一个函数String -&gt; ServantErr 可以用来转换错误吗?

标签: haskell monad-transformers servant


【解决方案1】:

假设您有一个函数strToServantErr :: String -&gt; ServantErr 用于将f,g,h 返回的错误转换为您的处理程序可以返回的错误,那么我们可以使用:

  • liftEitherEither String Ints 转换为ExceptT Strings。
  • withExceptT 根据Handler 的要求从ExceptT String 转换为ExceptT ServantErr
x1 <- withExceptT strToServantErr $ liftEither f

当您重复此操作 3 次时,我们可以使用 mapM 使其更整洁:

[x1, x2, x3] <- mapM (withExceptT strToServantErr . liftEither) [f, g, h]

现在我们已经对参数进行了排序,我们可以使用相同的想法来修复返回。将convertError 函数重命名为someErrorToServantErr 以保持一致性并假设它的类型为SomeError -&gt; ServantErr,那么我们可以这样做:

result <- liftIO $ a x1 x2 x3
withExceptT someErrorToServantErr $ liftEither result

我们解开aIO 计算,然后将其提升为ExceptT 并转换异常类型。

将一些代码整理成一个辅助函数后,我们得到了类似的结果:

myHandler :: Handler Text
myHandler = do
    [x1, x2, x3] <- mapM (liftMapE strToServantErr) [f, g, h]
    eitherResult <- liftIO $ a x1 x2 x3
    liftMapE someErrorToServantErr eitherResult
  where liftMapE f = withExceptT f . liftEither

这会尽快失败并根据需要转换错误,虽然它很密集但希望不是那么难以理解。


你也可以走 Applicative 路线,虽然我找不到让它特别好的方法(虽然我没有使用过 applicative functors,但我可能错过了一些有用的技巧):

myHandler :: Handler Text
myHandler = do
    let [x1, x2, x3] = map (liftMapE strToServantErr) [f, g, h] -- [Handler Int]
    tmp <- a <$> x1 <*> x2 <*> x3 -- IO (Either SomeError Text)
    eitherResult <- liftIO $ tmp
    liftMapE someErrorToServantErr eitherResult
  where liftMapE f = withExceptT f . liftEither

欢迎对上述代码进行任何改进!

【讨论】:

  • 您可以走得更远,在 exceptT monad(或类似的东西)中使用您的自定义错误类型并使用hoistServer(请参阅here)。
  • @AlpMestanogullari 哦,酷。我实际上对servant 一无所知,所以希望 OP 会看到你的评论并觉得它很有用。
【解决方案2】:

我相信 hnefatl 的回答中缺少 Handler 构造(假设该问题是关于 Servant 0.15 及左右的问题)。通知

newtype Handler a = Handler { runHandler' :: ExceptT ServantErr IO a }

这是我必须做的

eitherToHandler :: (e -> ServantErr) -> Either e a -> Handler a
eitherToHandler f = Handler . withExceptT f . liftEither

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-26
    • 1970-01-01
    • 2021-04-11
    • 1970-01-01
    • 1970-01-01
    • 2019-10-24
    • 2020-05-02
    • 1970-01-01
    相关资源
    最近更新 更多