J'aimerais que quelqu'un qui a une meilleure compréhension que moi me dise quelles sont les différences fondamentales entre Énumérateurs , Conduits y Tuyaux ainsi que les principaux avantages et inconvénients. Quelques-uns des sites la discussion est déjà en cours mais ce serait bien d'avoir une vue d'ensemble de haut niveau.
Réponses
Trop de publicités?Les énumérateurs/interétés en tant qu'abstraction ont été inventés par Oleg Kiselyov. Ils fournissent un moyen propre de faire des entrées/sorties avec des besoins en ressources prévisibles (faibles). Le paquetage actuel des Enumerators est assez proche du travail original d'Oleg.
Les conduits ont été créés pour le framework web Yesod. Je crois savoir qu'ils ont été conçus pour être extrêmement rapides. Les premières versions de la bibliothèque étaient très étatiques.
Les tuyaux se concentrent sur l'élégance. Ils n'ont qu'un seul type au lieu de plusieurs, forment des instances de monade (transformateur) et de catégorie, et sont très "fonctionnels" dans leur conception.
Si vous aimez les explications catégoriques : le Pipe
est juste la monade libre sur le foncteur simple et impie suivant
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
Dans la définition réelle des tuyaux, ces éléments sont intégrés, mais la simplicité de cette définition est étonnante. Les tuyaux forment une catégorie sous l'opération (<+<) :: Monad m => Pipe c d m r -> Pipe a b m r -> Pipe a d m r
qui prend n'importe quel premier tuyau yields
et l'achemine vers le deuxième tuyau en attente.
On dirait que Conduits
est en train de devenir plus Pipe
(utilisation de la CPS au lieu de l'état et passage à un type unique), tandis que les tuyaux bénéficient d'une meilleure gestion des erreurs et peut-être du retour à des types distincts pour les générateurs et les consommateurs.
Cette zone évolue rapidement. J'ai bricolé une variante expérimentale de la bibliothèque Pipes avec ces fonctionnalités, et je sais que d'autres personnes le font aussi (voir le paquet Guarded Pipes sur Hackage), mais je soupçonne Gabriel (l'auteur de Pipes) de les découvrir avant moi.
Mes recommandations : si vous utilisez Yesod, utilisez Conduits. Si vous voulez une bibliothèque mature, utilisez Enumerator. Si vous vous souciez principalement de l'élégance, utilisez Pipe.
Après avoir écrit des applications avec les trois bibliothèques, je pense que la plus grande différence que j'ai vue est dans la façon dont la finalisation des ressources est gérée. Par exemple, Pipes décompose la finalisation des ressources en deux types distincts, les Frames et les Stacks.
Il semble également qu'il y ait encore un débat sur la manière de finaliser non seulement la ressource d'entrée, mais aussi potentiellement la ressource de sortie. Par exemple, si vous lisez à partir d'une base de données et écrivez dans un fichier, la connexion à la base de données doit être fermée, tout comme le fichier de sortie doit être vidé et fermé. Les choses se compliquent lorsqu'il s'agit de décider comment gérer les exceptions et les échecs le long du pipeline.
Une autre différence plus subtile semble être la façon dont la valeur de retour du pipeline énumérateur est traitée et calculée.
Un grand nombre de ces différences et de ces incohérences potentielles ont été mises en évidence par l'utilisation des implémentations Monad et Category pour les Pipes et font maintenant leur chemin dans les Conduits.