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) a
EitherT
m
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
ます。外側のループを壊す計算はlift
ed です。
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
. ちなみに、私がEitherT
over をExceptT
主に使用しているのは、名前の負荷が少ないためです。どちらMFunctor
が最初にインスタンスを提供しても、私にとっては、最終的にどちらかのモナド変換子として勝ちます。