为什么没有ExceptT的MonadMask实例?

时间:2017-01-31 20:27:20

标签: haskell exception-handling monad-transformers

Edward Kmett的例外库未为MonadMask提供ExceptT个实例。

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

  

请注意,此程序包确实为MonadMask提供了CatchT个实例。如果基本monad不提供提供多个退出的功能,则此实例仅 有效。例如,IOEither将是无效的基础monad,但ReaderState是可以接受的。

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

Michael Snoyman also writes

  

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

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

3 个答案:

答案 0 :(得分:7)

下面是一个演示实例问题的程序:您可以使用Left提前退出,从而导致终结器永远不会运行。这与MonadMask文档中规定的法律形成对比,后者要求f `finally` g执行g 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")) 不会执行正确的参数。

std::string TimeStamp (const time_t seconds)  // version-1
{
  auto tm = *std::localtime(&seconds);  // <ctime>
  char readable[30] = {}; 
  std::strftime(&readable[0], sizeof(readable) - 1, "%Y-%h-%d %H:%M:%S:", &tm);
  return readable;
}

答案 1 :(得分:2)

  

monad的类,它提供了计算所有可能的退出点的能力,并掩盖了异步异常。基于连续的monad和诸如ErrorT e IO的堆栈(提供多种故障模式)是此类的无效实例。

ErrorT / ExceptTIO一起使用时,有多个退出点&#34;指的是您可以在Monad中引发运行时异常或异常。其中任何一个都会结束计算。

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

可以为MonadMask写一个对ExceptT有效的ExceptT e m a,前提是基础monad m 不< / em> IO。因此,关于将CatchTIO一起使用的大量警告(这样做会使MonadMask实例无效)。

答案 2 :(得分:0)

exceptions v0.9.0被上传到黑客 2018年2月25日以来,这似乎不再正确。

P.S。 0.9.0 被认为已弃用,建议使用 0.10.0 (请参见http://hackage.haskell.org/package/exceptions-0.10.0/changelog)。