haskell - ¿Estoy usando reactivo-banana verdad?
zeromq reactive-programming (1)
Aquí hay un ejemplo del programa Haskell FRP utilizando la biblioteca reactive-banana. Simplemente estoy empezando a sentirme a mi manera con Haskell, y especialmente no me he dado cuenta de lo que significa FRP. Realmente apreciaría alguna crítica del código de abajo
{-# LANGUAGE DeriveDataTypeable #-}
module Main where
{-
Example FRP/zeromq app.
The idea is that messages come into a zeromq socket in the form "id state". The state is of each id is tracked until it''s complete.
-}
import Control.Monad
import Data.ByteString.Char8 as C (unpack)
import Data.Map as M
import Data.Maybe
import Reactive.Banana
import System.Environment (getArgs)
import System.ZMQ
data Msg = Msg {mid :: String, state :: String}
deriving (Show, Typeable)
type IdMap = Map String String
-- | Deserialize a string to a Maybe Msg
fromString :: String -> Maybe Msg
fromString s =
case words s of
(x:y:[]) -> Just $ Msg x y
_ -> Nothing
-- | Map a message to a partial operation on a map
-- If the ''state'' of the message is "complete" the operation is a delete
-- otherwise it''s an insert
toMap :: Msg -> IdMap -> IdMap
toMap msg = case msg of
Msg id_ "complete" -> delete id_
_ -> insert (mid msg) (state msg)
main :: IO ()
main = do
(socketHandle,runSocket) <- newAddHandler
args <- getArgs
let sockAddr = case args of
[s] -> s
_ -> "tcp://127.0.0.1:9999"
putStrLn ("Socket: " ++ sockAddr)
network <- compile $ do
recvd <- fromAddHandler socketHandle
let
-- Filter out the Nothings
justs = filterE isJust recvd
-- Accumulate the partially applied toMap operations
counter = accumE M.empty $ (toMap . fromJust <$> justs)
-- Print the contents
reactimate $ fmap print counter
actuate network
-- Get a socket and kick off the eventloop
withContext 1 $ /ctx ->
withSocket ctx Sub $ /sub -> do
connect sub sockAddr
subscribe sub ""
linkSocketHandler sub runSocket
-- | Recieve a message, deserialize it to a ''Msg'' and call the action with the message
linkSocketHandler :: Socket a -> (Maybe Msg -> IO ()) -> IO ()
linkSocketHandler s runner = forever $ do
receive s [] >>= runner . fromString . C.unpack
Aquí hay una idea: https://gist.github.com/1099712 .
En particular, agradecería cualquier comentario sobre si este es un uso "bueno" de la acumulación, (no estoy seguro de que esta función atravesará todo el flujo de eventos cada vez, aunque supongo que no).
También me gustaría saber cómo se manejaría uno al tirar de los mensajes desde múltiples sockets, en este momento tengo un bucle de eventos dentro de una eternidad. Como ejemplo concreto de esto, ¿cómo agregaría un segundo socket (un par REQ / REP en el lenguaje zeromq) para consultar el estado actual del contador interno de IdMap?
(Autor de habla reactive-banana .)
En general, tu código me parece bien. En realidad, no entiendo por qué estás usando Banana reactiva en primer lugar, pero tendrás tus razones. Dicho esto, si está buscando algo como Node.js, recuerde que los hilos ligeros de Haskell hacen innecesario el uso de una arquitectura basada en eventos.
Anexo: Básicamente, la programación reactiva funcional es útil cuando tiene una variedad de diferentes entradas, estados y salidas que deben funcionar junto con la sincronización correcta (piense en las GUI, animaciones, audio). En contraste, es una exageración cuando se trata de muchos eventos esencialmente independientes; estos se manejan mejor con funciones ordinarias y el estado ocasional.
Con respecto a las preguntas individuales:
"Me encantaría cualquier comentario sobre si este es un uso" bueno "de la acumulación, (no estoy seguro de que esta función atravesará todo el flujo de eventos cada vez, aunque supongo que no)".
Luce bien para mi. Como has adivinado, la función de accumE
es de hecho en tiempo real; Sólo almacenará el valor acumulado actual.
A juzgar por lo que crees, parece que piensas que cada vez que entra un nuevo evento, viajará a través de la red como una luciérnaga. Si bien esto sucede internamente, no es así como debe pensar acerca de la programación reactiva funcional. Más bien, la imagen correcta es la siguiente: el resultado de fromAddHandler
es la lista completa de eventos de entrada, tal como ocurrirán . En otras palabras, debe pensar que recvd
contiene la lista ordenada de todos y cada uno de los eventos del futuro. (Por supuesto, en aras de su propia cordura, no debe intentar mirarlos antes de que haya llegado su hora ;-)) La función de accumE
simplemente transforma una lista en otra al atravesarla una vez.
Tendré que aclarar más esta forma de pensar en la documentación.
"También me gustaría saber cómo se haría para obtener mensajes desde múltiples sockets, en este momento tengo un bucle de eventos dentro de una eternidad. Como ejemplo concreto de esto, ¿cómo agregaría un segundo socket (un par REQ / REP? en zeromq parlance) para consultar el estado actual del IdMap dentro del contador? "
Si la función de receive
no se bloquea, simplemente puede llamarlo dos veces en diferentes sockets
linkSocketHandler s1 s2 runner1 runner2 = forever $ do
receive s1 [] >>= runner1 . fromString . C.unpack
receive s2 [] >>= runner2 . fromString . C.unpack
Si se bloquea, deberá usar subprocesos, consulte también la sección Manejo de múltiples transmisiones TCP en el libro Real World Haskell. (No dude en hacer una nueva pregunta sobre esto, ya que está fuera del alcance de este).