4

たとえば、...

consumer :: Proxy p => () -> Consumer p a (EitherT String IO) ()
producer :: Proxy p => () -> Producer p a (EitherT ByteString IO) r

...どうすればこれを機能させることができますか?

session :: EitherT ByteString (EitherT String IO) ()
session = runProxy $ producer >-> consumer

注:でミキシングベースモナドを読みましたControl.Proxy.Tutorial。最初の例を取得しましたが、考案された例を理解できません。より多くの例は、それほど明白ではありませんが、それほど工夫されていないので、ベースモナドの任意の組み合わせの使用方法hoistと一致方法を明確にする可能性があります。lift

4

3 に答える 3

8

ベースモナドMT1 MT2 MT3 M aがどこにあるかのようなモナド変換子スタックがあるとします。M

を使用liftして、左側に新しいモナド変換子を追加できます。任意のトランスフォーマーにすることができるので、でシンボル化しましょう?

lift :: MT1 MT2 MT3 M a -> ? MT1 MT2 MT3 M a

を使用しhoistて、左端の要素の右側にあるモナドスタックを操作できます。どのようにそれを操作しますか?たとえば、lift:を指定します。

hoist lift :: MT1 MT2 MT3 M a -> MT1 ? MT2 MT3 M a

hoistとの組み合わせを使用してlift、これらの「ワイルドカード」をモナド変換子スタックの任意の場所に挿入できます。

hoist (hoist lift) :: MT1 MT2 MT3 M a -> MT1 MT2 ? MT3 M a

hoist (hoist (hoist lift)) :: MT1 MT2 MT3 M a -> MT1 MT2 MT3 ? M a

この手法を使用して、例の2つのモナドスタックを均等化できます。

于 2013-02-14T15:09:34.183 に答える
5

どちらのパッケージ(あなたのEitherTタイプが由来していると思います)は、最初の引数を変更するためのいくつかの関数を提供します。

bimapEitherT :: Functor m => (e -> f) -> (a -> b) -> EitherT e m a -> EitherT f m b

これをいくつかの適切なエンコード(またはデコード)とともに使用して、をEitherT String IO aEitherT ByteString IO aまたはその逆)に変換し、次にhoistその変換をConsumerまたはProducerモナド変換子に変換できます。

于 2013-02-14T14:36:23.643 に答える
5

実際には2つの解決策があります。

最初の解決策は、Daniel Wagnerが提案したものです。同じLeftタイプを使用するように、2つのベースモナドを変更します。たとえば、両方を使用するように正規化できますByteString。これを行うには、最初にByteStringpack関数を使用します。

pack :: String -> ByteString

EitherT次に、それを持ち上げて、 :の左側の値を処理します。

import Control.Error (fmapLT)  -- from the 'errors' package

fmapLT pack :: (Monad m) => EitherT String m r -> EitherT ByteString m r

次に、以下を使用して、その変換をConsumerベースモナドにターゲティングする必要がありhoistます。

hoist (fmapLT pack)
 :: (Monad m, Proxy p)
 => Consumer p a (EitherT String m) r -> Consumer p a (EitherT ByteString m) r

同じベースモナドを持っているので、プロデューサーと直接コンシューマーを構成できるようになりました。

2番目の解決策は、DanielDiazCarreteが提案したものです。EitherT代わりに、2つのパイプで、両方のレイヤーを含む共通のモナド変換子スタックについて合意します。あなたがしなければならないのは、これらの2つのレイヤーをネストする順序を決定することだけです。

EitherT String変圧器の外側に変圧器を重ねることを選択したと想像してみましょうEitherT ByteString。つまり、最終的なターゲットモナド変換子スタックは次のようになります。

(Proxy p) => Session (EitherT String (EitherT ByteString p)) IO r

次に、その変圧器スタックをターゲットにするために、両方のパイプをプロモートする必要があります。

あなたの場合、その間にレイヤーConsumerを挿入する必要があり、その最終的なモナド変換子スタックと一致させたい場合。レイヤーの作成は簡単です。を使用するだけですが、これら2つの特定のレイヤーの間のリフトをターゲットにする必要があるため、プロキシモナド変換子とモナド変換子の両方をスキップする必要があるため、を2回使用します。EitherT ByteStringEitherT StringIOlifthoistEitherT String

hoist (hoist lift) . consumer
 :: Proxy p => () -> Consumer p a (EitherT String (EitherT ByteString IO)) ()

最終的なモナド変換子スタックを一致させたい場合は、プロキシモナド変換子と変換子の間にレイヤーProducerを挿入する必要があります。繰り返しになりますが、レイヤーの作成は簡単です。を使用するだけですが、これら2つの特定のレイヤーの間のリフトをターゲットにする必要があります。あなたはただ、しかし今回は一度だけそれを使用します、なぜならあなたは正しい場所に寄り添うためにプロキシモナド変換子をスキップする必要があるだけだからです:EitherT StringEitherT ByteStringlifthoistlift

hoist lift . producer
 :: Proxy p => () -> Producer p a (EitherT String (EitherT ByteString IO)) r

これで、プロデューサーとコンシューマーは同じモナド変換子スタックを持ち、それらを直接構成できます。

さて、あなたは疑問に思うかもしれません:このhoistingliftのプロセスは「正しいこと」をしているのでしょうか?答えはイエスです。圏論の魔法の一部は、を使用して「空のモナド変換子層」を正しく挿入することの意味をlift厳密に定義できることです。同様に、hoistいくつかを指定することにより、 「2つのモナド変換子の間にあるものをターゲットにする」ことの意味を厳密に定義できます。理論的に着想を得た法律とそれを検証しlifthoistそれらの法律を遵守します。

liftこれらの法則を満たせば、正確に何をするかについての本質的な詳細をすべて無視することができhoistます。圏論は、モナド変換子の間に空間的に「リフトを挿入する」という観点から考える非常に高い抽象化レベルで作業することを可能にし、コードは私たちの空間的直感を厳密に正しい動作に魔法のように変換します。

EitherT単一のレイヤーでプロデューサーとコンシューマーの間でエラー処理を共有できるため、おそらく最初のソリューションが必要になると思います。

于 2013-02-14T17:14:52.023 に答える