Enumerators、Conduits、およびPipesの基本的な違いと、主な利点と欠点について、自分よりも深く理解している人から聞きたいです。一部の議論はすでに 進行中ですが、大まかな概要を把握できれば幸いです。
2 に答える
抽象化としての列挙子/反復子は、Oleg Kiselyov によって発明されました。これらは、予測可能な (低い) リソース要件で IO を実行するクリーンな方法を提供します。現在の Enumerators パッケージは、Oleg のオリジナル作品にかなり近いものです。
Yesod Web フレームワーク用にコンジットが作成されました。私の理解では、それらは非常に高速になるように設計されています。ライブラリの初期バージョンは非常にステートフルでした。
エレガンスを重視したパイプ。それらは、複数ではなく 1 つのタイプ、フォーム モナド (トランスフォーマー) およびカテゴリ インスタンスを持ち、設計上非常に「機能的」です。
カテゴリ的な説明が好きな場合:Pipe
型は、次の信じられないほど単純なファンクター上のフリー モナドです。
data PipeF a b m r = M (m r) | Await (a -> r) | Yield b r
instance Monad m => Functor (PipeF a b m) where
fmap f (M mr) = M $ liftM mr
fmap f (Await g) = Await $ f . g
fmap f (Yield b p) = Yield b (f p)
--Giving:
newtype Pipe a b m r = Pipe {unPipe :: Free (PipeF a b m) r}
deriving (Functor, Applicative, Monad)
--and
instance MonadTrans (Pipe a b) where
lift = Pipe . inj . M
実際のパイプ定義ではこれらが組み込まれていますが、この定義の単純さは驚くべきものです。(<+<) :: Monad m => Pipe c d m r -> Pipe a b m r -> Pipe a d m r
パイプは、最初のパイプを受け取り、yields
それを待機中の 2 番目のパイプに供給する 操作の下にカテゴリを形成します。
Conduits
(状態の代わりに CPS を使用し、単一の型に切り替える)より似たものに移行しているように見えますがPipe
、パイプはより良いエラー処理のサポートを獲得しており、おそらくジェネレーターとコンシューマーに別々の型を返すようになっています。
このエリアは急速に動いています。私は、これらの機能を備えたパイプ ライブラリの実験的なバリアントをハッキングしてきました。他の人も同様であることを知っています (Hackage の Guarded Pipes パッケージを参照)。行う。
私の推奨事項: Yesod を使用している場合は、Conduits を使用してください。成熟したライブラリが必要な場合は、列挙子を使用します。エレガンスを第一に考えている場合は、パイプを使用してください。
3 つのライブラリすべてを使用してアプリケーションを作成した後、私が目にした最大の違いは、リソースのファイナライズの処理方法にあると思います。たとえば、パイプはリソースのファイナライズをフレームとスタックの別々のタイプに分割します。
入力リソースだけでなく、潜在的に出力リソースをファイナライズする方法についても、まだいくつかの議論があるようです。たとえば、DB から読み取り、ファイルに書き込む場合、DB の接続を閉じる必要があるだけでなく、出力ファイルをフラッシュして閉じる必要があります。パイプラインに沿って例外や失敗のケースを処理する方法を決定するとき、物事は複雑になります。
もう 1 つの微妙な違いは、列挙子パイプラインの戻り値の処理方法と計算方法です。
これらの違いと潜在的な矛盾の多くは、パイプのモナドとカテゴリの実装を使用することで明らかになり、現在コンジットに組み込まれています。