为什么没有ExceptT的MonadMask实例?

Chr*_*tin 14 haskell exception-handling monad-transformers

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

Ben Gamari曾经问过这件事,然后得出结论,文件对此进行了解释.这是我能找到的最接近相关的段落:

请注意,此包确实提供了一个MonadMask实例CatchT.此实例在基本monad无法提供多个退出时才有效.例如,IO或者Either是无效的基础monad,但是Reader或者State是可以接受的.

但它的含义对我来说并不是不言而喻的."多重退出"意味着什么,为什么禁止MonadMask实例?

迈克尔斯诺曼还写道:

[...]'MonadMask',它允许您保证运行某些操作,即使存在异常(同步和异步).为了提供该保证,monad堆栈必须能够控制其执行流程.特别是,这排除了具有多个退出点的Monads的实例,例如ErrorTover IO.

或许可以更清楚地提出这个替代问题:如果我们搁置变压器并考虑稍微简单的类型:

data IOEither a = IOEither { unIOEither :: IO (Either String a) }
    deriving Functor
Run Code Online (Sandbox Code Playgroud)

似乎是我们其实可以写一个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)
Run Code Online (Sandbox Code Playgroud)

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

coc*_*ure 7

下面是一个演示实例问题的程序:您可以提前退出Left,从而导致终结器永远不会运行.这与文档中所述的法律相反,MonadMask后者要求f `finally` g g执行for 而不管发生什么f.终结器永远不会运行的原因很简单:如果没有抛出异常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"))
Run Code Online (Sandbox Code Playgroud)