2011-07-25 17 views
17

Aquí hay un ejemplo del programa FRP de Haskell que usa la biblioteca de plátano reactivo. Solo estoy empezando a sentirme a gusto con Haskell, y especialmente no me he dado cuenta de lo que significa FRP. Me encantaría recibir alguna crítica del código de abajo¿Estoy usando plátano reactivo, verdad?

{-# 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 

Hay una esencia aquí: https://gist.github.com/1099712.

Me encantaría especialmente cualquier comentario sobre si este es un "buen" uso de accumE, (no estoy seguro de que esta función recorra todo el flujo de eventos cada vez, aunque supongo que no).

También me gustaría saber cómo se podría ir sobre tirando en mensajes de tomas múltiples - en el momento tengo un bucle de eventos dentro de un siempre. Como un ejemplo concreto de esto, ¿cómo agregaría el segundo socket (un par REQ/REP en el lenguaje zeromq) para consultar el estado actual del contador interno IdMap?

Respuesta

21

(Autor de reactive-banana habla.)

En general, el código se ve muy bien a mí. En realidad, no entiendo por qué estás usando plátano reactivo, pero tendrás tus razones. Dicho esto, si está buscando algo como Node.js, recuerde que los hilos leightweight de Haskell make it unnecessary utilizan una arquitectura basada en eventos.

Adición: Básicamente, la programación funcional reactivo es útil cuando se tiene una variedad de diferentes entradas, los estados y de salida que deben trabajar juntos con sólo el momento adecuado (piensa interfaces gráficas de usuario, animaciones, audio). Por el contrario, es excesivo cuando se trata de muchos eventos esencialmente independientes; estos se manejan mejor con funciones ordinarias y el estado ocasional.


En cuanto a las preguntas individuales:

"sería especialmente bienvenida a cualquier comentario en torno a si se trata de un 'buen uso' de accumE, (estoy claro de esta función atravesará el conjunto secuencia de eventos cada vez, aunque supongo que no). "

se ve bien para mí. Como habrás adivinado, la función accumE es de hecho en tiempo real; solo almacenará el valor acumulado actual.

A juzgar por su conjetura, parece estar pensando que cada vez que entre un nuevo evento, viajará a través de la red como una luciérnaga. Si bien esto ocurre internamente, es no cómo debería pensar sobre programación reactiva funcional. Más bien, la imagen de la derecha es la siguiente: el resultado de fromAddHandler es la lista completa de eventos de entrada , ya que 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 interés de su propia cordura, no debería tratar de mirarlos antes de que llegue su hora; ;-)) La función accumE simplemente transforma una lista en otra atravesando una vez.

Tendré que dejar esta forma de pensar más clara en la documentación.

"También me gustaría saber cómo se pueden obtener mensajes de varios sockets, por el momento tengo el bucle de eventos dentro de un para siempre.Como ejemplo concreto de esta ¿cómo iba a añadir segunda toma (un REQ/REP par en la jerga zeromq) para consultar el estado actual de la idMap dentro contador?"

Si la función receive no bloquea, se sólo tiene que llamar dos veces en diferentes tomas

linkSocketHandler s1 s2 runner1 runner2 = forever $ do 
    receive s1 [] >>= runner1 . fromString . C.unpack 
    receive s2 [] >>= runner2 . fromString . C.unpack 

Si no cuadra, que tendrá que utilizar hilos, consulta la sección Handling Multiple TCP Streams en el libro real World Haskell. (no dude en hacer una nueva pregunta sobre este tema, ya que está fuera del alcance de este.)

+0

Gracias Heinrich, la razón th En FRP parecía un buen ajuste aquí es que si tiene muchos zócalos zeromq, podrían fácilmente comenzar a parecerse a la entrada guiada por eventos desde una GUI ... Así que pensé en patear los neumáticos de algunas ideas nuevas :-) ¿Creerías que tiene más sentido hacer esto con una mónada estatal y los hilos haskell? –

+0

@ Ben Ford: He agregado un pequeño comentario a la respuesta. No sé qué quiere hacer con los enchufes, por lo que no puedo decirle si FRP es excesivo para su propósito. Básicamente, si su red de eventos no crecerá mucho más grande que esta con un solo 'accumE' y algunos 'filtersE', entonces se realizará de forma más elegante sin FRP. –

Cuestiones relacionadas