Frage

Hier ist ein Beispiel für Haskell FRP-Programm mit der Reactive-Banana-Bibliothek. Ich fange gerade erst an, mich mit Haskell in den Weg zu fühlen und habe mich besonders nicht ganz darum herumgekommen, was FRP bedeutet. Ich würde eine Kritik des Code unten wirklich schätzen

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

Hier gibt es ein Kern: https://gist.github.com/1099712.

Ich würde besonders alle Kommentare darüber begrüßen, ob dies eine "gute" Verwendung von Accume ist (ich bin mir nicht sicher, dass diese Funktion jedes Mal den gesamten Ereignisstrom durchquert, obwohl ich nicht vermute).

Außerdem würde ich gerne wissen, wie man Nachrichten von mehreren Steckdosen einziehen würde - im Moment habe ich eine Ereignisschleife in einem für immer. Wie würde ich als konkretes Beispiel dafür einen zweiten Socket (ein Rep/Rep -Paar im Zeromq -Sprachgebrauch) hinzufügen, um den aktuellen Zustand des IDMAP in der Zähler abzufragen?

War es hilfreich?

Lösung

(Autor von Reaktive-Banana Apropos.)

Insgesamt sieht Ihr Code für mich gut aus. Ich verstehe eigentlich nicht, warum Sie in erster Linie Reaktiv-Banana verwenden, aber Sie werden Ihre Gründe haben. Wenn Sie jedoch nach so etwas wie Node.js suchen, denken Sie daran, dass Haskells Leightweight -Threads Machen Sie es unnötig Verwendung einer ereignisbasierten Architektur.

Addendum: Grundsätzlich ist die funktionelle reaktive Programmierung nützlich, wenn Sie eine Vielzahl verschiedener Eingaben, Zustände und Ausgaben haben, die mit genau dem richtigen Timing zusammenarbeiten müssen (denken Sie an GUIS, Animationen, Audio). Im Gegensatz dazu ist es übertrieben, wenn Sie mit vielen im Wesentlichen unabhängigen Ereignissen zu tun haben. Diese werden am besten mit gewöhnlichen Funktionen und gelegentlichem Zustand behandelt.


In Bezug auf die individuellen Fragen:

"Ich würde besonders alle Kommentare darüber begrüßen, ob dies eine" gute "Verwendung von Accume ist (ich bin mir nicht sicher, dass diese Funktion jedes Mal den gesamten Ereignisstrom durchquert, obwohl ich nicht vermute)."

Sieht gut aus für mich. Wie Sie erraten haben, die accumE Funktion ist in der Tat Echtzeit; Es speichert nur den aktuellen akkumulierten Wert.

Nach Ihrer Vermutung scheinen Sie zu denken, dass es wie ein Feuerfly durch das Netzwerk fließt, wenn ein neues Ereignis hereinkommt. Während dies intern passiert, ist es das ist es nicht Wie du solltest denken Über funktionale reaktive Programmierung. Vielmehr lautet das richtige Bild: das Ergebnis von fromAddHandler ist die vollständige Liste der Eingabeereignisse wie sie passieren werden. Mit anderen Worten, Sie sollten das denken recvd Enthält die geordnete Liste aller Ereignisse aus der Zukunft. (Natürlich sollten Sie im Interesse Ihrer eigenen geistigen Gesundheit nicht versuchen, sie anzusehen, bevor ihre Zeit gekommen ist. ;-)) Die accumE Funktion transformiert einfach eine Liste in eine andere, indem sie sie einmal durchquert.

Ich muss diese Art, in der Dokumentation klarer zu denken, klarer machen.

"Außerdem würde ich gerne wissen, wie man Nachrichten von mehreren Steckdosen einziehen würde - im Moment, in dem ich für immer auf Ereignisschleife habe. im Zeromq -Sprachgebrauch), um den aktuellen Zustand des IDMAP im Zähler abzufragen? "

Wenn die receive Funktion blockiert nicht, Sie können es einfach zweimal auf verschiedenen Sockets aufrufen

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

Wenn es blockiert ist, müssen Sie Threads verwenden, siehe auch den Abschnitt Umgang mit mehreren TCP -Streams In dem Buch Real World Haskell. (Fühlen Sie sich frei, eine neue Frage dazu zu stellen, da sie außerhalb des Rahmens dieses Spiels liegt.)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top