haskell - 在像 exceptT a IO 这样的 monad 堆栈中管理资源的最佳方法是什么?

标签 haskell exception resources monad-transformers io-monad

无论好坏,Haskell 的流行 Servant库使得在涉及 ExceptT err IO 的 monad 转换器堆栈中运行代码变得很常见。 Servant 自己的处理程序 monad 是 ExceptT ServantErr IO。正如许多人所说,这有点 troublesome monad因为有多种方式导致无法展开:1) 通过来自底层 IO 的正常异常,或 2) 通过返回 Left.

如 Ed Kmett 的 exceptions图书馆helpfully clarifies :

Continuation-based monads, and stacks such as ErrorT e IO which provide for multiple failure modes, are invalid instances of this [MonadMask] class.

这非常不方便,因为 MonadMask 使我们能够访问有用的 [多态版本] bracket 函数来进行资源管理(不会由于异常等而泄漏资源)。 )。但在 Servant 的 Handler monad 中我们无法使用它。

我对此不太熟悉,但有人说解决方案是使用 monad-control ,它有很多合作伙伴库,例如 lifted-baselifted-async 使您的 monad 能够访问 bracket 等资源管理工具(大概这也适用于 ExceptT err IO 和 friend ?)。

但是,monad-control 似乎是 losing favor in the community ,但我不知道替代方案是什么。甚至 Snoyman 最近的 safe-exceptions 库也使用 Kmett 的 exceptions 库并避免 monad-control

有人可以为像我这样试图认真使用 Haskell 的人澄清当前的故事吗?

最佳答案

您可以在IO中工作,在最后返回一个IO(Either ServantErr r)类型的值并将其包装在ExceptT中使其适合处理程序类型。这将使您可以在 IO 中正常使用 bracket。这种方法的一个问题是您失去了 ExceptT 提供的“自动错误管理”功能。也就是说,如果您在处理程序中间失败,则必须对 Either 以及类似的事情执行显式模式匹配。

<小时/>

上面基本上是重新实现ExceptTMonadTransControl实例,即

instance MonadTransControl (ExceptT e) where
    type StT (ExceptT e) a = Either e a
    liftWith f = ExceptT $ liftM return $ f $ runExceptT
    restoreT = ExceptT

monad-control 在提升像 bracket 这样的函数时工作正常,但它有一些奇怪的极端情况,函数如下(摘自 this blog post ):

import Control.Monad.Trans.Control

callTwice :: IO a -> IO a
callTwice action = action >> action

callTwice' :: ExceptT () IO () -> ExceptT () IO ()
callTwice' = liftBaseOp_ callTwice

如果我们传递给 callTwice' 一个打印某些内容的操作,然后立即失败

main :: IO ()
main = do
    let printAndFail = lift (putStrLn "foo") >> throwE ()
    runExceptT (callTwice' printAndFail) >>= print  

无论如何,它都会打印“foo”两次,即使我们的直觉表明它应该在第一次执行操作失败后停止。

<小时/>

另一种方法是使用 resourcet库并在 ExceptT ServantErr (ResourceT IO) r monad 中工作。您需要使用 resourcet 函数,例如 allocate而不是 bracket,并在末尾调整 monad,如:

import Control.Monad.Trans.Resource
import Control.Monad.Trans.Except

adapt :: ExceptT ServantErr (ResourceT IO) r -> ExceptT err IO r 
adapt = ExceptT . runResourceT . runExceptT

或类似:

import Control.Monad.Morph

adapt' :: ExceptT err (ResourceT IO) r -> ExceptT err IO r 
adapt' = hoist runResourceT

关于haskell - 在像 exceptT a IO 这样的 monad 堆栈中管理资源的最佳方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40372087/

相关文章:

haskell - 为什么 MonadReader r (StateT s m) 使用底层 monad 的实例

Haskell - 无法将预期类型 ‘b’ 与实际类型 ‘a’ 匹配

android - 如何从android中的另一个应用程序资源更改主题?

Haskell - 跨度 elem : evaluation

haskell - 如何使用 Control.Lens 中的 IndexedTraversal 为每个元素执行索引感知操作?

c# - 索引超出绑定(bind)异常

language-agnostic - 如何处理异常

java - 构建失败的android,不推荐使用的功能

Android:原始资源是否存储在本地文件系统上?

angularjs - Angular 资源自定义 url