抽象化は、ダイアモンド トポロジーまたは のような動作pipe
の形式をサポートしません。Arrow
これは API の問題ではありませんが、そのようなシナリオでは正しい、または明確に定義された動作はありません。
理由を説明するために、図を次のように簡略化させてください。
+----+
| pL |
+----+ => +----+ => +----+
| p1 | | p2 |
+----+ => +----+ => +----+
| pR |
+----+
p1
私たちがパイプにいて、にいると想像してrespond
くださいpL
。チュートリアルを覚えていれば、プロキシの法則により、すべてのrespond
ブロックが上流までブロックされる必要があります。つまり、再び sp1
まで制御を取り戻すことはできませんpL
request
。したがって、この時点で次のことがわかります。
p1
送信者を待ってブロックされましrequest
たpL
ただし、それpL
はrequest
まだではなく、代わりにrespond
独自の値 to を持っているとしp2
ます。これで、次のようになりました。
p1
送信者を待ってブロックされましrequest
たpL
pL
送信者を待ってブロックされましrequest
たp2
ここで、p2
代わりにrequest
が fromであるとしpR
ます。代理の法則では、再び sp2
まで制御を取り戻すことはできないとされていpR
respond
ます。これで、次のようになりました。
p1
送信者を待ってブロックされましrequest
たpL
pL
送信者を待ってブロックされましrequest
たp2
p2
送信者を待ってブロックされましrespond
たpR
pR
request
から値を取得するとどうなりp1
ますか? ブロックのリストを調べてみると、p1
はまだ from を待っている状態でブロックされているため、request
frompL
を受け取るにはrequest
適していませんpR
。と が同じ署名pL
をpR
共有していたとしても、いわば「結び目を結ぶ」正しい方法はありません。request
より一般的には、代理法則は次の 2 つの不変条件を保証します。
- アクティブなパイプの「上流」にあるすべてのパイプは、
respond
- アクティブなパイプの「下流」のすべてのパイプは、
request
サイクルまたはダイアモンドは、これらの不変条件を破ります。これが、循環トポロジーが「意味を成さない」ことをチュートリアルで非常に簡単に述べている理由です。
先ほど示した例で、ダイヤモンドがこの不変条件を破る理由がわかります。p1
制御が の上流にあった場合、 でブロックされたpR
ことを意味します。ただし、制御を取得したときは の下流にありました。これは、 でブロックされたことを意味します。これは矛盾につながります。なぜなら、制御が流れて に到達しないため、まだ変更できていないからです。pR
request
p2
pR
pR
respond
pR
pL
pR
p2
機械
したがって、問題には2つの解決策があります。1 つの解決策は、目的の分割動作を 1 つのパイプにインライン化することです。とpE
の動作を 1 つのパイプに結合するパイプを定義します。pL
pR
この問題に対するより洗練された解決策は、エドワードのスタイルの何かですmachines
。をサポートするプロキシよりも強力ではない、より制限された抽象化を定義しArrowChoice
、その抽象化のドメイン内で矢印のようなことを行い、完了したらプロキシにアップグレードします。
目を細めると、Haskell で現在利用可能なコルーチン抽象化のカテゴリが半順序であるふりをすることができます。コルーチンの抽象化はオブジェクトであり、コルーチンの抽象化からコルーチンの抽象化への矢印はC1
、型のコルーチンC2
に型のコルーチンを埋め込むことができることを意味します(つまり、 は の不適切なサブセットです)。C1
C2
C1
C2
この部分的な順序では、プロキシはおそらくターミナル オブジェクトです。つまり、プロキシはコルーチンのアセンブリ言語と考えることができます。アセンブリ言語のアナロジーに従うと、プロキシは提供する保証が少なくなりますが、より限定的なコルーチンの抽象化 (つまり、高レベル言語) をプロキシ内に埋め込むことができます。これらの高水準言語は、より強力な抽象化 (つまりArrow
インスタンス) を可能にするより大きな制限を提供します。
この簡単な例が必要な場合は、最も単純なコルーチンの抽象化の 1 つである Kleisli 矢印を検討してください。
newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }
instance Category (Kleisli m) where
id = Kleisli return
(Kleisli f) . (Kleisli g) = Kleisli (f <=< g)
Kleisli の矢印は、プロキシよりも確実に制限されていますが、この制限があるため、Arrow
インスタンスをサポートしています。したがって、インスタンスが必要なときはいつでもArrow
、Kleisli の矢印を使用してコードを記述し、それをArrow
記法を使用して結合します。完了したら、以下を使用して、その高レベルの Kleisli コードをプロキシ アセンブリ コードに「コンパイル」できますmapMD
。
kleisliToProxy :: (Proxy p) => Kleisli m a b -> () -> Pipe p a b m r
kleisliToProxy (Kleisli f) = mapMD f
このコンパイルは、ファンクターの法則に従います。
kleisliToProxy id = idT
kleisliToProxy (f . g) = kleisliToProxy f <-< kleisliToProxy g
したがって、分岐コードを矢印で記述できる場合は、コードのそのセクションに矢印をKleisli
使用Kleisli
し、完了したらプロキシにコンパイルします。このトリックを使用すると、複数のコルーチン抽象化をプロキシ抽象化にコンパイルして、それらを混合できます。