Haskell 变压器组底部插入错误

Haskell 变压器组底部插入错误,haskell,error-handling,monads,monad-transformers,Haskell,Error Handling,Monads,Monad Transformers,从tioamonad中运行类型为t(ErrorT String 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

tioa
monad中运行类型为
t(ErrorT String 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
模块示例,其中
导入系统.IO
导入控制.Monad.Reader
导入控制.Monad.Error
类型Env=String
内部::ReaderT Env(错误字符串IO)()
内部=do

请注意,正如Edward所说,将
ErrorT
放在堆栈的顶部而不是底部通常要简单得多

这可能会改变堆栈的语义,至少对于比
ReaderT
更复杂的转换器-例如,如果堆栈中有
StateT
,则底部的
error
在出现错误时将回滚状态更改,而顶部的
error
,出现错误时,将保留对状态的更改

如果您确实需要在底部使用它,那么类似的内容将通过类型检查器:

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
import Control.Monad.Error
导入控制.Monad.Morph
导入系统.IO
toOuter::MFunctor t=>t(错误字符串IO)a->t IO a
toOuter=起重机运行错误打印
runErrorTWithPrint::错误字符串IO a->IO a
runErrorTWithPrint m=do
雷斯多
hPutStrLn标准错误
失败错误
右v->返回v
请注意,当内部计算失败时,它调用
fail
,这不是上面的代码所做的

主要原因是要使用
起重机
,我们需要为所有a提供
类型的功能。错误字符串IO a->IO a
-即处理任何类型的值,而不仅仅是
()
。这是因为取决于monad堆栈的其余部分可能意味着当您到达
error
时的实际返回类型与您开始使用的返回类型不同

在失败案例中,我们没有类型为
a
的值,因此一个选项是失败

在您的原始代码中,您还可以无限地在
外部
中循环,但这不起作用。

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

这里的问题是你选择了分层。如果将
错误
移到外部,则在这种情况下
失败
时,它将正常工作。一般来说,将变压器堆栈视为某种量子波形,直到最后一分钟才应该崩溃

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
internal::monawarder Env m=>m()
内部=do
s m()
外部=do
res liftIO$hPutStrLn标准错误
右键->返回()
外面的
注意,一切都变得简单多了。没有提升,没有明确的提升,娜达<代码>内部
在不同的monad中运行,我们扩展了当前的monad,不管它是什么,外部是error

通过不显式地选取堆栈,您可以最大化可以使用代码的情况的数量


如果你一定要这么做,那么遵循Ganesh的道路,但是仔细想想你是否真的需要在你描述的情况下变形

外层真的应该是一个无限循环吗?@GaneshSittampalam为什么不呢?Haskell很懒惰,因此
runReaderT
将产生一个有用的
IO
操作IO操作可能很有用,因为它在每次迭代或类似的过程中都会与用户交互-只是我从问题中想,外部应该与内部类似,但删除了error字符串。谢谢。不幸的是,正如我刚才在问题中所写的,我在我的一些堆栈中使用了
StateT
,而宁愿在下面使用
error
,而不是在上面。那么您可能会遇到一个涉及
提升的解决方案。谢谢。我想我应该在问题中说得更清楚,我希望
外部
是一个无限循环。我想我最好的选择是使用类似于
catch和gnoreeexception$(提升机运行错误twithprint)的内部
,这感觉很难看,但我想它会起作用。我看到的其他选项是:(a)将“fail err”更改为“returnundefined”,并希望堆栈中没有任何内容实际检查返回值。(b) 尝试编写类似于MFunctor/high的东西,但是使用更严格的类型,并处理更小的monad集,这样runErrorTWithPrint就不需要是多态的。然而,我怀疑斯塔特会击败这两个选项。我同意,这太难看了。但既然我大学的学期结束后,这个项目就没用了,那就行了。我刚刚检查了返回undefined不起作用。
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