¿Cuáles son las ventajas y desventajas de enumeradores, conductos y tuberías?
Me gustaría saber de alguien con un conocimiento más profundo que yo cuáles son las diferencias fundamentales entre enumeradores , conductos y tuberías , así como los principales beneficios y desventajas. Ya hay algunas discusiones en curso , pero sería bueno tener una descripción general de alto nivel.
Los enumeradores/iterados como abstracción fueron inventados por Oleg Kiselyov. Proporcionan una forma limpia de realizar IO con requisitos de recursos predecibles (bajos). El paquete Enumerators actual es bastante parecido al trabajo original de Oleg.
Se crearon conductos para el marco web de Yesod. Tengo entendido que fueron diseñados para ser increíblemente rápidos. Las primeras versiones de la biblioteca tenían mucho estado.
Las pipas se centran en la elegancia. Tienen un solo tipo en lugar de varios, forman instancias de mónada (transformador) y categoría, y tienen un diseño muy "funcional".
Si le gustan las explicaciones categóricas: el Pipe
tipo es solo la mónada libre sobre el siguiente funtor simple e impío
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
En la definición de tubería real, estos están integrados, pero la simplicidad de esta definición es sorprendente. Las tuberías forman una categoría bajo la operación (<+<) :: Monad m => Pipe c d m r -> Pipe a b m r -> Pipe a d m r
que toma el primer tubo yields
y lo alimenta al segundo tubo en espera.
Parece que se Conduits
está volviendo más Pipe
parecido (usando CPS en lugar de estado y cambiando a un solo tipo), mientras que Pipes está ganando soporte para un mejor manejo de errores y tal vez el regreso de tipos separados para generadores y consumidores.
Esta área se está moviendo rápidamente. He estado pirateando una variante experimental de la biblioteca Pipe con estas características, y sé que otras personas también lo están haciendo (consulte el paquete Guarded Pipes en Hackage), pero sospecho que Gabriel (el autor de Pipes) las descubrirá antes de que yo hacer.
Mis recomendaciones: si estás usando Yesod, usa Conduits. Si desea una biblioteca madura, utilice Enumerator. Si lo que más te importa es la elegancia, utiliza Pipe.
Después de escribir aplicaciones con las tres bibliotecas, creo que la mayor diferencia que he visto es cómo se maneja la finalización de los recursos. Por ejemplo, Pipes divide la finalización de recursos en tipos separados de Marcos y Pilas.
También parece haber todavía cierto debate sobre cómo finalizar no sólo el recurso de entrada, sino también potencialmente el recurso de salida. Por ejemplo, si está leyendo desde una base de datos y escribiendo en un archivo, la conexión de la base de datos debe cerrarse, así como el archivo de salida debe vaciarse y cerrarse. Las cosas se ponen complicadas a la hora de decidir cómo manejar las excepciones y los casos de error a lo largo del proceso.
Otra diferencia más sutil parece ser cómo se maneja y calcula el valor de retorno de la canalización del enumerador.
Muchas de estas diferencias y posibles inconsistencias han quedado expuestas mediante el uso de las implementaciones Monad y Category para Pipes y ahora están llegando a Conduits.