15

ドキュメントControl.Monad.Trans.Errorは、2 つのモナドを結合するこの例を提供します。

type ErrorWithIO e a = ErrorT e IO a
==> ErrorT (IO (Either e a))

これは直観に反していると思います:ラッピングErrorTしていると思われますが、エラー情報がIO アクションの結果の型に挿入されているように見えます。私はそれを期待していただろう IO

==> ErrorT (Either e (IO a))

「ラップ」という言葉の通常の意味に基づいています。

問題をさらに混乱させるためにStateT、それぞれのいくつかを実行します。

type MyError e = ErrorT e Identity  -- (see footnote)
type StateWithError s e a = StateT s (MyError e) a
==> StateT (s -> ErrorT (Either e (a, s)))

の側に状態型sが注入されていますが、全体も関数でラップされています。EitherRightEither

モナドが逆に結合されている場合、事態をさらに混乱させるために:

type ErrorWithState e s a = ErrorT e (State s) a
==> ErrorT (StateT (s -> (Either e a, s)))

「外側」はまだ関数です。Either e (s -> (a, s))状態関数がエラー型内にネストされているようなものは生成されません。

これらすべてに根底にある論理的な一貫性があることは確かですが、よくわかりません。その結果、個々のモナドの意味を理解するのに問題がなくても、あるモナドを別のモナドと組み合わせることが何を意味するのかを考えるのは難しいと思います。

誰かが私を啓発できますか?


(脚注:説明のために、とが互いに一致するように構成ErrorTしています。通常は、レイヤーを使用するだけで済みます。IdentityStateWithErrorErrorWithStateStateWithError s e a = StateT s (Either e) aErrorT

4

2 に答える 2

18

これは直感に反していると思います。ErrorT は IO をラップしていると思われますが、IO アクションの結果の型にエラー情報が挿入されているように見えます。

モナドトランスフォーマーは一般に、適用されるモナドを「ラップ」しません。少なくとも明白な意味ではありません。それを「ラッピング」と考えると、ファンクターの構成が暗示されますが、これは特にここで起こっていないことです。

State s説明すると、 とのファンクター構成はMaybe、定義を展開すると次のようになります。

newtype StateMaybe s a = StateMaybe (s -> (Maybe a, s))    -- == State s (Maybe a)
newtype MaybeState s a = MaybeState (Maybe (s -> (a, s)))  -- == Maybe (State s a)

State最初のケースでは、正常に動作Nothingし、状態値に影響を与えないことに注意してください。2 番目のケースでは、単純なState関数があるか、まったくないかのどちらかです。どちらの場合も、2 つのモナドの特徴的な動作が実際に組み合わされることはありません。これは驚くべきことではありません。なぜなら、これらは、あるモナドを他のモナド内で使用される通常の値として単純に使用することによって得られるものと同じだからです。

これを と比較してくださいStateT s Maybe:

newtype StateTMaybe s a = StateTMaybe (s -> Maybe (a, s))

この場合、この 2 つは一緒に織り込まれています。Stateをヒットしない限り、通常の方法で が進行します。ヒットNothingした場合、計算は中止されます。これは、上記のケースとは根本的に異なります。これが、そもそもモナド変換子が存在する理由でもあります。それらを単純に構成しても、特別な機械は必要ありません。それらは互いに独立して動作するからです。


どちらが「外側」にあるのかを理解する限り、モナド内の値を扱うとき、ある意味で、「外側」のトランスフォーマーは、ある意味で、動作が「優先」されるトランスフォーマーであると考えるのに役立つかもしれません。 「内部」モナドは、通常どおりのビジネスしか見ません。これが常に最も内側にある理由であることに注意してくださいIO-それはそのビジネスで他のことを起こさせませんが、仮想的なトランスフォーマーは、ラップされたモナドがトークンIOTの複製や破棄など、あらゆる種類のシェナニガンをプルできるようにする必要があります。RealWorld

  • StateTReaderTどちらも関数の結果の周りに「内部」モナドを置きます。変換されたモナドに到達する前に、状態値または環境を提供する必要があります。

  • MaybeT両方とも、変換されたモナドの内部ErrorTに滑り込み、値が存在しない可能性があることを除いて、通常の方法で動作できるようにします。

  • Writerは完全に受動的であり、動作にまったく影響を与えないため、モナドの値に自分自身を付加するだけです。

  • ContT結果の型をラップするだけで、変換されたモナドの処理を完全に先延ばしにして、物事をそれ自体に保持します。

それはちょっと面倒ですが、ああ、モナド変換子はその場しのぎのようなもので、そもそも混乱を招きます。特定の選択に対してきちんとした理論的正当性があるかどうかはわかりませんが、それらが機能し、通常は 2 つのモナドの組み合わせ (合成ではなく) を実行したいという事実以外にはありません。

その結果、個々のモナドの意味を理解するのに問題がなくても、あるモナドを別のモナドと組み合わせることが何を意味するのかを考えるのは難しいと思います。

ええ、これは期待できることのように聞こえますが、残念です。

于 2011-08-17T05:36:55.223 に答える
5

ErrorT が想像どおりに定義されているとしたら、どうなるか考えてみてください。失敗する IO アクションをどのようにエンコードしますか? アクションが失敗したときに値を指定Either e (IO a)することはできません。これは、アクションに到達した時点で、それが値であることはすでに明らかであるためです。そうでなければ、それはアクションではありません。LeftRight

IO (Either e a)ただし、これは当てはまりません。Left全体が IO アクションになり、エラーを示す値を返すことができます。他の人が指摘したように、モナド変換子をラッパーと考えないでください。むしろそれらを関数と考えてください。彼らはモナドを取り、それを別のモナドに変えます。それらはモナドを変換します。

于 2011-08-17T08:54:36.380 に答える