您正在寻找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) aEitherTm
over :: (MonadTrans t, Monad m) => EitherT e m a -> EitherT e (t m) a
over = mapEitherT lift
内层EitherT将像变压器堆栈中的任何其他底层 monad 一样被处理。我们可以lift_EitherT i m aEitherT 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,这是一种经常使用的模式,它值得拥有自己经过深思熟虑的名称。顺便说一句,我使用EitherToverExceptT主要是因为它的名称较少。对我来说,无论哪个先提供MFunctor实例,最终都会胜出,成为单子变换器。