您正在寻找EitherT
或ExceptT
。它增加了两种返回变压器堆栈的方法。计算可以是return a
或throwError e
。错误和返回之间有两个区别。错误保存在 上Left
并返回 上Right
。当您>>=
遇到错误时,它会短路。
newtype EitherT e m a = EitherT { runEitherT :: m (Either e a) }
return :: a -> EitherT e m a
return a = EitherT $ return (Right a)
throwError :: e -> EitherT e m a
throwError e = EitherT $ return (Left a)
我们还将使用名称left = throwError
和right = return
.
错误Left
不会继续,我们将使用它们来表示退出循环。我们将使用该类型EitherT r m ()
来表示一个循环,该循环要么以中断结果停止,要么Left r
以Right ()
. 这几乎完全是forever
,除了我们打开EitherT
并去掉Left
返回值周围的 。
import Control.Monad
import Control.Monad.Trans.Either
untilLeft :: Monad m => EitherT r m () -> m r
untilLeft = liftM (either id id) . runEitherT . forever
在充实您的示例后,我们将回到如何使用这些循环。
由于您希望看到几乎所有逻辑都消失了,因此我们也将EitherT
用于其他所有内容。获取数据的计算要么是要么Done
返回数据。
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
data Done = Done deriving Show
-- Gets numbers for a while.
get1 :: EitherT Done (State Int) Int
get1 = do
x <- lift get
lift . put $ x + 1
if x `mod` 3 == 0
then left Done
else right x
放置数据的第一个计算是 aFailure
或返回。
data Failure = Failure deriving Show
put1 :: Int -> EitherT Failure (State Int) ()
put1 x = if x `mod` 16 == 0
then left Failure
else right ()
放置数据的第二个计算是 aSuccess
或返回。
data Success = Success deriving Show
put2 :: EitherT Success (State Int) ()
put2 = do
x <- lift get
if x `mod` 25 == 0
then left Success
else right ()
对于您的示例,我们将需要组合两个或多个以不同方式异常停止的计算。我们将用两个嵌套EitherT
的 s 来表示。
EitherT o (EitherT i m) r
外部EitherT
是我们目前正在操作的那个。我们可以通过在每个†</sup>周围添加一个额外的层来将 an 转换EitherT o m a
为 an 。EitherT o (EitherT i m) a
EitherT
m
over :: (MonadTrans t, Monad m) => EitherT e m a -> EitherT e (t m) a
over = mapEitherT lift
内层EitherT
将像变压器堆栈中的任何其他底层 monad 一样被处理。我们可以lift
_EitherT i m a
EitherT o (EitherT i m) a
我们现在可以构建一个成功或失败的整体计算。运行会破坏当前循环的计算over
。会破坏外部循环的计算被lift
编辑。
example :: EitherT Failure (State Int) Success
example =
untilLeft $ do
lift . untilLeft $ over get1 >>= lift . put1
over put2
总体Failure
被lift
编入最内层循环两次。这个例子很有趣,可以看到一些不同的结果。
main = print . map (runState $ runEitherT example) $ [1..30]
†</sup>如果EitherT
有一个MFunctor
实例,那over
就是hoist lift
,这是一种经常使用的模式,它值得拥有自己经过深思熟虑的名称。顺便说一句,我使用EitherT
overExceptT
主要是因为它的名称较少。对我来说,无论哪个先提供MFunctor
实例,最终都会胜出,成为单子变换器。