在变压器堆栈的基础上插入ErrorT

时间:2013-12-22 04:17:00

标签: haskell error-handling monads monad-transformers

t (ErrorT String IO) a monad中运行类型为t IO a的代码的最佳方法是什么?请考虑以下代码:

module Sample where

import System.IO
import Control.Monad.Reader
import Control.Monad.Error

type Env = String

inner :: ReaderT Env (ErrorT String IO) ()
inner = do
    s <- ask
    fail s

outer :: ReaderT Env IO ()
outer = do
    env <- ask
    res <- lift $ runErrorT $ runReaderT inner env
    case res of
        Left err -> liftIO $ hPutStrLn stderr err
        Right _ -> return ()
    outer

这有效,但我一直在寻找一种更优雅的方式在我的堆栈底部插入ErrorT。特别是我在我的项目中使用了几个不同的monad变换器堆栈并且为每个变换器堆栈编写上述内容非常繁琐。

我正在寻找类似的东西:

outer :: ReaderT Env IO ()
outer = do
    res <- (hoist runErrorT) inner
    ...

但由于类型不匹配,我无法使用hoist


编辑:

我在某些筹码中使用StateT,这就是尝试将ErrorT放在基座而不是顶部的原因。

outer应该是无限循环。

2 个答案:

答案 0 :(得分:5)

请注意,正如爱德华所说,将ErrorT置于堆栈顶部而非底部通常要简单得多。

这可以改变堆栈的语义,至少对于比ReaderT更复杂的变换器 - 例如如果堆栈中有StateT,那么当底部有ErrorT时,状态的更改将在出现错误时回滚,而在顶部有ErrorT时,状态会发生变化将在出现错误时保留。

如果你真的需要它在底部,那么像这样的东西通过类型检查器:

import Control.Monad.Error
import Control.Monad.Morph
import System.IO

toOuter :: MFunctor t => t (ErrorT String IO) a -> t IO a
toOuter = hoist runErrorTWithPrint

runErrorTWithPrint :: ErrorT String IO a -> IO a
runErrorTWithPrint m = do
   res <- runErrorT m
   case res of
       Left err -> do
           hPutStrLn stderr err
           fail err
       Right v -> return v

请注意,当内部计算失败时,它会调用fail,这不是上面的代码所做的。

主要原因是要使用hoist,我们需要提供forall a . ErrorT String IO a -> IO a类型的函数 - 即处理任何类型的值,而不仅仅是()。这是因为取决于monad堆栈的其余部分可能意味着当你到达ErrorT时实际的返回类型与你开始时的返回类型不同。

在失败的情况下,我们没有类型a的值,因此一个选项是失败。

在您的原始代码中,您也会在outer中无限循环,但这不会。

答案 1 :(得分:5)

这里的正确答案是“不要那样做”。

这里的问题是你正在挑选分层。如果您将Error移到外面,则在这种情况下fail的行为正常。一般认为变压器堆栈是某种量子波形,直到最后一刻才能崩溃。

inner :: MonadReader Env m => m ()
inner = do
  s <- ask
  fail s

outer :: (MonadReader Env m, MonadIO m) => m ()
outer = do
  res <- runErrorT inner
  case res of
    Left err -> liftIO $ hPutStrLn stderr err
    Right _  -> return ()
  outer

注意一切都变得多么简单。没有吊装,没有明确的提升,虚无... inner运行在一个不同的monad中,我们已经扩展了当前的monad,无论它是什么,外部都有ErrorT。

通过不明确选择堆栈,可以最大化可以使用代码的情况数。

如果您必须这样做,那么请遵循Ganesh的路径,但要认真考虑是否确实需要改变您描述的情况!