Haskell 除了IO之外,管理monad堆栈中资源的最佳方法是什么?

Haskell 除了IO之外,管理monad堆栈中资源的最佳方法是什么?,haskell,exception-handling,resources,monad-transformers,io-monad,Haskell,Exception Handling,Resources,Monad Transformers,Io Monad,不管是好是坏,Haskell流行的库已经成为在monad transformer堆栈中运行涉及Except err IO的代码的常见场所。仆人自己的处理程序monad是除了ServantErr IO。正如许多人认为的那样,这是一个需要解决的问题,因为有多种方法可以使失败展开:1)通过基本的IO的正常异常,或2)通过返回Left 作为Ed Kmett的图书馆: 基于延续的monad和提供多种故障模式的堆栈(如error e IO)是该[MonadMask]类的无效实例 这非常不方便,因为Monad

不管是好是坏,Haskell流行的库已经成为在monad transformer堆栈中运行涉及
Except err IO
的代码的常见场所。仆人自己的处理程序monad是
除了ServantErr IO
。正如许多人认为的那样,这是一个需要解决的问题,因为有多种方法可以使失败展开:1)通过基本的
IO
的正常异常,或2)通过返回
Left

作为Ed Kmett的图书馆:

基于延续的monad和提供多种故障模式的堆栈(如
error e IO
)是该[
MonadMask
]类的无效实例

这非常不方便,因为
MonadMask
允许我们访问有用的[polymorphic version]
bracket
函数来进行资源管理(而不是由于异常等原因泄漏资源)。但是在仆人的
Handler
monad中,我们不能使用它

我对它不是很熟悉,但有人说解决方案是使用
monad控件
,它有许多合作伙伴库,如
lifted base
lifted async
,让monad访问资源管理工具,如
bracket
(这大概适用于
除了错误IO
和朋友吗?)

然而,
monad控件
似乎是,但我不知道替代方案是什么。即使是斯诺曼最近的
安全异常
库也使用Kmett的
异常
库,避免使用
monad控件


有人能为像我这样的人澄清一下当前的情况吗?他们正试图让我们认真使用Haskell?

您可以在
IO
中工作,返回
IO类型的值(要么是servanter,要么是servanter)
结尾,并将其包装在
ExceptT
中,以使其适合处理程序类型。这将允许您在
IO
中正常使用
括号。这种方法的一个问题是您失去了“自动错误管理”“<代码>例外> <代码>提供。也就是说,如果在处理程序中间失败,则必须在<代码>上执行显式模式匹配。

以上内容基本上是为
ExceptT
重新实现
MonadTransControl
实例,即

instance MonadTransControl (ExceptT e) where
    type StT (ExceptT e) a = Either e a
    liftWith f = ExceptT $ liftM return $ f $ runExceptT
    restoreT = ExceptT
monad控件在提升功能(如
支架
)时工作正常,但它具有以下功能(取自):

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

main :: IO ()
main = do
    let printAndFail = lift (putStrLn "foo") >> throwE ()
    runExceptT (callTwice' printAndFail) >>= print  
不管怎样,它会打印两次“foo”,即使我们的直觉说它应该在第一次执行操作失败后停止


另一种方法是使用库并在
除ServantErr(ResourceT IO)r
单子中工作。您需要使用
ResourceT
函数,如而不是
括号
,并在末尾调整单子,如:

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

我的建议是:让你的代码活在IO中而不是ExceptT中,并将每个处理程序函数包装在一个
ExceptT中。尝试一下

非常好的响应!因此似乎没有真正的“标准”解决此问题的方法?在这种情况下,monad控件具有边缘情况是有道理的,因为根据Kmett的说法,它可能无法为这些堆栈提供良好的实现。在
ExceptT err(ResourceT IO)r
ResourceT(ExceptT err IO)之间有区别吗r
.AFAICS
ResourceT
只是一个阅读器,所以它应该可以通勤;但是还有其他含义吗?@phadej我相信这两个命令都可以,但我觉得
除了err(ResourceT IO)r
更自然。经过深思熟虑和研究,我必须承认我得出了这个结论,在某些方面,这似乎是一个逃避,但却是最明确、最无痛的解决方案。仅仅因为它更简单并不意味着它是错的。我真的认为人们正在使整个问题变得比需要的更复杂,因为模糊的本埃菲茨。
import Control.Monad.Morph

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