Why is there no MonadMask instance for ExceptT?

前端 未结 3 565
你的背包
你的背包 2021-02-19 07:39

Edward Kmett\'s exceptions library does not provide a MonadMask instance for ExceptT.

Ben Gamari once asked about this and then concluded that it was explained

相关标签:
3条回答
  • 2021-02-19 08:26

    A class for monads which provide for the ability to account for all possible exit points from a computation, and to mask asynchronous exceptions. Continuation-based monads, and stacks such as ErrorT e IO which provide for multiple failure modes, are invalid instances of this class.

    When you use ErrorT/ExceptT with IO, having "multiple exit points" refers to the fact that you can have either a runtime exception or an exception thrown in the Monad. Either of which would end the computation.

    runExceptT $ do
      error "This is an exit point."
      throwError "This is another exit point."
      return 23
    

    It would be possible to write a MonadMask for ExceptT that would be valid for all ExceptT e m a with the precondition that the underlying monad m is not IO. Hence the huge warning about using CatchT with IO (Doing so invalidates the MonadMask instance).

    0 讨论(0)
  • 2021-02-19 08:33

    Below is a program that demonstrates the problem with your instances: You can exit early with Left and thereby cause the finalizer to never be run. This is in contrast to the law stated in the docs for MonadMask which require that for f `finally` g g is executed regardless of what happens in f. The reason why the finalizer is never run is quite simple: If no exception is thrown finally (or bracket which is how finally is implemented) just uses >>= to run the finalizer afterwards but >>= does not execute the right argument if the left returns 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"))
    
    0 讨论(0)
  • 2021-02-19 08:40

    It seems it's no longer true since exceptions v0.9.0 which has been uploaded to the hackage 25 Feb 2018.

    P.S. 0.9.0 is considered deprecated, 0.10.0 is recommended to use (see http://hackage.haskell.org/package/exceptions-0.10.0/changelog).

    0 讨论(0)
提交回复
热议问题