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