haskell - ¿Cuál es la conexión entre Iteratees y FRP?
conduit haskell-pipes (2)
Me parece que hay una fuerte conexión entre las dos ideas. Mi conjetura es que FRP podría implementarse en términos de iterados si hubiera una manera de expresar gráficos arbitrarios con iterados. Pero afaik solo soportan estructuras en forma de cadena.
¿Podría alguien arrojar algo de luz sobre esto?
Es al revés. Hay una fuerte conexión entre AFRP y el procesamiento de secuencias. De hecho, AFRP es una forma de procesamiento de flujo, y puedes usar el lenguaje para implementar algo muy similar a tuberías:
data Pipe m a b =
Pipe {
cleanup :: m (),
feed :: [a] -> m (Maybe [b], Pipe m a b)
}
Esa es una extensión de las categorías de cables que se encuentran en Netwire. Recibe el siguiente fragmento de entrada y devuelve Nothing cuando deja de producir. Usando esto, un lector de archivos tendría el siguiente tipo:
readFile :: (MonadIO m) => FilePath -> Pipe m a ByteString
Pipe es una familia de functors aplicativos, por lo que para aplicar una función simple a los elementos de la secuencia, simplemente puede usar fmap :
fmap (B.map toUpper) . readFile
Para su comodidad, también es una familia de profesores.
La característica más interesante es que esta es una familia de funtores alternativos. Eso le permite enrutar los flujos y permitir que varios procesadores de flujo "intenten" antes de darse por vencidos. Esto se puede extender a una biblioteca de análisis completa que incluso puede utilizar cierta información estática para fines de optimización.
Puede implementar una forma limitada de FRP utilizando procesadores de flujo. Por ejemplo, al usar la biblioteca de pipes
, puede definir una fuente de eventos:
mouseCoordinates :: (Proxy p) => () -> Producer p MouseCoord IO r
... y podría definir de manera similar un controlador gráfico que toma las coordenadas del mouse y actualiza un cursor en un lienzo:
coordHandler :: (Proxy p) => () -> Consumer p MouseCoord IO r
Luego conectaría los eventos del mouse al controlador utilizando la composición:
>>> runProxy $ mouseCoordinates >-> coordHandler
Y se ejecutaría de la manera que esperas.
Como ha dicho, esto funciona bien para una sola cadena de etapas, pero ¿qué pasa con las topologías más arbitrarias? Bueno, resulta que dado que el tipo de pipes
Proxy
central es un transformador de mónada, puede modelar cualquier topología arbitraria simplemente anidando transformadores de mónada proxy sobre ellos mismos. Por ejemplo, aquí es cómo se comprimen dos flujos de entrada:
zipD
:: (Monad m, Proxy p1, Proxy p2, Proxy p3)
=> () -> Consumer p1 a (Consumer p2 b (Producer p3 (a, b) m)) r
zipD () = runIdentityP $ hoist (runIdentityP . hoist runIdentityP) $ forever $ do
a <- request () -- Request from the outer Consumer
b <- lift $ request () -- Request from the inner consumer
lift $ lift $ respond (a, b) -- Respond to the Producer
Esto se comporta como una función al curry. Lo aplicas parcialmente a cada entrada secuencialmente y luego puedes ejecutarlo cuando está completamente aplicado.
-- 1st application
p1 = runProxyK $ zipD <-< fromListS [1..]
-- 2nd application
p2 = runProxyK $ p2 <-< fromListS [4..6]
-- 3rd application
p3 = runProxy $ printD <-< p3
Funciona de la manera que esperas:
>>> p3
(1, 4)
(2, 5)
(3, 6)
Este truco generaliza a cualquier topología. Puede encontrar muchos más detalles sobre esto en Control.Proxy.Tutorial en la sección "Ramas, cremalleras y fusiones". En particular, debe revisar el combinador de fork
que utiliza como ejemplo, lo que le permite dividir un flujo en dos salidas.