EitherTまたはを探していExceptTます。Transformer スタックに戻る 2 つの方法を追加します。計算は、 または のいずれreturn aかthrowError eです。エラーとリターンには 2 つの違いがあります。エラーは で保持され、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。
Don't continueのエラー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
データを配置する最初の計算は、Failureまたは戻り値のいずれかです。
data Failure = Failure deriving Show
put1 :: Int -> EitherT Failure (State Int) ()
put1 x = if x `mod` 16 == 0
then left Failure
else right ()
データを配置する 2 番目の計算は、Successまたは戻り値のいずれかです。
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 ()
あなたの例では、異なる方法で例外的に停止する 2 つ以上の計算を組み合わせる必要があります。これを 2 つEitherTのネストされた で表します。
EitherT o (EitherT i m) r
外側EitherTは、現在操作中のものです。すべての†</sup>の周りに追加のレイヤーを追加することで、 anEitherT 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レイヤーは、トランスフォーマー スタック内の他の基礎となるモナドと同じように扱われます。私たちはliftするEitherT i m aことができますEitherT o (EitherT i m) a
これで、成功または失敗する全体的な計算を構築できます。現在のループを破る計算が実行されoverます。外側のループを壊す計算はlifted です。
example :: EitherT Failure (State Int) Success
example =
untilLeft $ do
lift . untilLeft $ over get1 >>= lift . put1
over put2
全体Failureはlift、最も内側のループに 2 回追加されます。この例は、いくつかの異なる結果を見るのに十分興味深いものです。
main = print . map (runState $ runEitherT example) $ [1..30]
†</sup>EitherTにMFunctorインスタンスがあれば、overただhoist lift. ちなみに、私がEitherTover をExceptT主に使用しているのは、名前の負荷が少ないためです。どちらMFunctorが最初にインスタンスを提供しても、私にとっては、最終的にどちらかのモナド変換子として勝ちます。