10

パイプの概念のさまざまな実装の違いを理解しようとしています。コンジットパイプの違いの 1 つは、パイプを融合する方法です。コンジット

(>+>) :: Monad m
      => Pipe l a b r0 m r1 -> Pipe Void b c r1 m r2 -> Pipe l a c r0 m r2

パイプが持っている間

(>->) :: (Monad m, Proxy p)
      => (b' -> p a' a b' b m r) -> (c' -> p b' b c' c m r) -> c' -> p a' a c' c m r

私が正しく理解している場合、pipesでは、2 つのパイプのいずれかが停止すると、その結果が返され、もう一方が停止します。コンジットでは、左のパイプが終了すると、その結果が下流の右のパイプに送信されます。

私は、conduitのアプローチの利点は何だろうか? コンジットおよびを使用して実装するのは簡単ですが、パイプおよび>+>を使用して実装するのは(より)難しい例(できれば実世界)を見たいと思います。>->

4

2 に答える 2

9

The classic example of something easier to implement with conduit currently is handling end of input from upstream. For example, if you want to fold a list of values and bind the result within the pipeline, you cannot do it within pipes without engineering an extra protocol on top of pipes.

In fact, this is precisely what the upcoming pipes-parse library solves. It engineers a Maybe protocol on top of pipes and then defines convenient functions for drawing input from upstream that respect that protocol.

For example, you have the onlyK function, which takes a pipe and wraps all outputs in Just and then finishes with a Nothing:

onlyK :: (Monad m, Proxy p) => (q -> p a' a b' b m r) -> (q -> p a' a b' (Maybe b) m r)

You also have the justK function, which defines a functor from pipes that are Maybe-unaware to pipes that are Maybe-aware for backwards compatibility

justK :: (Monad m, ListT p) => (q -> p x a x b m r) -> (q -> p x (Maybe a) x (Maybe b) m r)

justK idT = idT
justK (p1 >-> p2) = justK p1 >-> justK p2

And then once you have a Producer that respects that protocol you can use a large variety of parsers that abstract over the Nothing check for you. The simplest one is draw:

draw :: (Monad m, Proxy p) => Consumer (ParseP a p) (Maybe a) m a

It retrieves a value of type a or fails in the ParseP proxy transformer if upstream ran out of input. You can also take multiple values at once:

drawN :: (Monad m, Proxy p) => Int -> Consumer (ParseP a p) (Maybe a) m [a]

drawN n = replicateM n draw  -- except the actual implementation is faster

... and several other nice functions. The user never actually has to directly interact with the end of input signal at all.

Usually when people ask for end-of-input handling, what they really wanted was parsing, which is why pipes-parse frames end-of-input issues as a subset of parsing.

于 2013-03-06T22:23:35.523 に答える
5

私の経験では、アップストリーム ターミネータの実際の利点は非常に小さいため、現時点ではパブリック API から隠されています。私はそれらを 1 つのコード (wai-extra のマルチパート解析) でしか使用しなかったと思います。

最も一般的な形式では、Pipe を使用すると、出力値のストリームと最終結果の両方を生成できます。その Pipe を別の下流の Pipe と融合すると、その出力値のストリームが下流の入力ストリームになり、上流の最終結果が下流の「上流ターミネータ」になります。したがって、その観点から、任意のアップストリーム ターミネータを使用すると、対称 API が可能になります。

しかし実際には、そのような機能が実際に使用されることは非常にまれであり、API を混乱させるだけであるため、1.0 リリースでは .Internal モジュールに隠されていました。理論上の使用例の 1 つとして、次のようなものがあります。

  • バイトのストリームを生成する Source があります。
  • バイトのストリームを消費し、最終結果としてハッシュを計算し、すべてのバイトを下流に渡すコンジット。
  • ファイルに保存するなど、バイトのストリームを消費するシンク。

アップストリーム ターミネータを使用すると、これら 3 つを接続して、コンジットからの結果をパイプラインの最終結果として返すことができます。ただし、ほとんどの場合、同じ目標を達成するための別のより簡単な手段があります。この場合、次のことができます。

  1. conduitFileバイトをファイルに格納し、ハッシュ コンジットをハッシュ シンクに変換して下流に配置するために使用します。
  2. zipSinksを使用して、ハッシュ シンクとファイル書き込みシンクの両方を 1 つのシンクにマージします。
于 2013-03-07T05:15:13.510 に答える