haskell - 为什么 exceptT 没有 MonadMask 实例?

标签 haskell exception monad-transformers

Edward Kmett 的异常库不提供MonadMask ExceptT 的实例.

Ben Gamari once asked about this然后得出结论,文档对此进行了解释。这是我能找到的最接近相关的段落:

Note that this package does provide a MonadMask instance for CatchT. This instance is only valid if the base monad provides no ability to provide multiple exit. For example, IO or Either would be invalid base monads, but Reader or State would be acceptable.

但它的意义对我来说并不是不言而喻的。 “多次退出”是什么意思以及为什么它禁止 MonadMask 实例?

Michael Snoyman also writes :

[...] 'MonadMask', which allows you to guarantee that certain actions are run, even in the presence of exceptions (both synchronous and asynchronous). In order to provide that guarantee, the monad stack must be able to control its flow of execution. In particular, this excludes instances for [...] Monads with multiple exit points, such as ErrorT over IO.

也许问这个替代问题会更清楚:如果我们搁置变压器并考虑稍微简单的类型:

data IOEither a = IOEither { unIOEither :: IO (Either String a) }
    deriving Functor

看起来我们实际上可以编写一个 MonadMask 实例:

instance Applicative IOEither where
    pure = IOEither . return . Right
    IOEither fIO <*> IOEither xIO = IOEither $
        fIO >>= either (return . Left) (\f -> (fmap . fmap) f xIO)

instance Monad IOEither where
    IOEither xIO >>= f = IOEither $
        xIO >>= either (return . Left) (\x -> unIOEither (f x))

instance MonadThrow IOEither where
    throwM e = IOEither (throwM @IO e)

instance MonadCatch IOEither where
    catch (IOEither aIO) f = IOEither $ catch @IO aIO (unIOEither . f)

instance MonadMask IOEither where
    mask f = IOEither $ mask @IO $ \restore ->
        unIOEither $ f (IOEither . restore . unIOEither)
    uninterruptibleMask f = IOEither $ uninterruptibleMask @IO $ \restore ->
        unIOEither $ f (IOEither . restore . unIOEither)

我写的这个实例不能正常工作吗?

最佳答案

下面的程序演示了您的实例的问题:您可以使用 Left 提前退出,从而导致终结器永远不会运行。这与 MonadMask 文档中规定的法律相反,该法律要求无论发生什么情况,都执行 f `finally` g gf中。终结器永远不会运行的原因非常简单:如果没有抛出异常 finally (或 bracket 这是 finally 的实现方式)之后使用 >>= 运行终结器,但如果左侧返回 Left,则 >>= 不会执行右侧参数。

data IOEither a = IOEither { unIOEither :: IO (Either String a) }
    deriving Functor

instance Applicative IOEither where
    pure = IOEither . return . Right
    IOEither fIO <*> IOEither xIO = IOEither $
        fIO >>= either (return . Left) (\f -> (fmap . fmap) f xIO)

instance Monad IOEither where
    IOEither xIO >>= f = IOEither $
        xIO >>= either (return . Left) (\x -> unIOEither (f x))

instance MonadThrow IOEither where
    throwM e = IOEither (throwM @IO e)

instance MonadCatch IOEither where
    catch (IOEither aIO) f = IOEither $ catch @IO aIO (unIOEither . f)

instance MonadMask IOEither where
    mask f = IOEither $ mask @IO $ \restore ->
        unIOEither $ f (IOEither . restore . unIOEither)
    uninterruptibleMask f = IOEither $ uninterruptibleMask @IO $ \restore ->
        unIOEither $ f (IOEither . restore . unIOEither)

instance MonadIO IOEither where
  liftIO x = IOEither (Right <$> x)

main :: IO ()
main = void $ unIOEither $ finally (IOEither (return (Left "exit")))
                                   (liftIO (putStrLn "finalizer"))

关于haskell - 为什么 exceptT 没有 MonadMask 实例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41966893/

相关文章:

haskell - FFI 中的可变数据和惰性

Haskell:显示时抑制字符串周围的引号

haskell - 复数仿函数和monad的含义和用法?

exception - 测试 Twig 模板中是否存在路由 (Symfony 2)

java - 性能 : Checked and unchecked exceptions in JAVA

php 尝试...否则

haskell - 与 monad 中的值进行模式匹配

haskell - 将 MonadReader/MonadError 实例添加到 Transformer 类型

parsing - Haskell:用二进制懒惰地读取二进制文件

haskell - 限制效果,例如 `Freer` ,使用 MTL 样式