Czy używam reaktywnego banana?

Oto przykładowy program Haskell FRP wykorzystujący bibliotekę reactive-banana. Dopiero zaczynam odczuwać moją drogę do Haskella, a zwłaszcza nie do końca rozumiem, co oznacza FRP. Naprawdę byłbym wdzięczny za krytykę poniższego kodu

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

Jest tu gist: https://gist.github.com/1099712 .

Chciałbym szczególnie mile widziane wszelkie uwagi dotyczące tego, czy jest to "dobre" wykorzystanie accumE, (nie jestem pewien, że ta funkcja będzie przemierzać cały strumień zdarzeń za każdym razem, chociaż zgaduję, że nie).

Również chciałbym wiedzieć, jak można przejść o wyciąganie wiadomości z wielu gniazd - w tej chwili mam jedną pętlę zdarzeń wewnątrz forever. Jako konkretny przykład tego, jak dodać drugie gniazdo (parę REQ/REP w slangu zeromq) do zapytania do aktualnego stanu IdMap wewnątrz licznika?

Author: HaskellElephant, 2011-07-25

1 answers

(Autorreactive-banan mówi.)

Ogólnie, Twój kod wygląda dobrze dla mnie. Nie rozumiem, dlaczego w ogóle używasz reaktywnego banana, ale będziesz miał swoje powody. To powiedziawszy, jeśli szukasz czegoś takiego jak węzeł.js, pamiętaj, że wątki Haskella sprawiają, że używanie architektury opartej na zdarzeniach nie jest konieczne.

Dodatek: zasadniczo funkcjonalne programowanie reaktywne jest przydatne, gdy masz wiele różnych wejścia, stany i wyjścia, które muszą współpracować z odpowiednim wyczuciem czasu (GUI, animacje, dźwięk). W przeciwieństwie do tego, to przesada, gdy mamy do czynienia z wieloma zasadniczo niezależnymi zdarzeniami; najlepiej radzić sobie z tymi zwykłymi funkcjami i okazjonalnym stanem.


Dotyczące poszczególnych pytań:

"chciałbym szczególnie mile widziane wszelkie uwagi dotyczące tego, czy jest to "dobre" wykorzystanie accumE, (nie jestem pewien, że ta funkcja będzie przemierzać całe zdarzenie stream za każdym razem, chociaż zgaduję, że nie)."

Jak dla mnie wygląda dobrze. Jak się domyślasz, funkcja accumE rzeczywiście działa w czasie rzeczywistym; przechowuje tylko aktualną skumulowaną wartość. Sądząc po twoich przypuszczeniach, wydaje ci się, że myślisz, że za każdym razem, gdy pojawi się nowe wydarzenie, będzie ono podróżować przez sieć jak Świetlik. Podczas gdy dzieje się to wewnętrznie, to nie jest to Jak powinieneś myśleć o funkcyjnym programowaniu reaktywnym. Raczej właściwe zdjęcie jest takie: wynik fromAddHandler jest kompletną listą zdarzeń wejściowych tak, jak się one wydarzą. Innymi słowy, powinieneś pomyśleć, że recvd zawiera uporządkowaną listę każdego wydarzenia z przyszłości. (Oczywiście, w interesie własnego rozsądku, nie powinieneś próbować patrzeć na nich, zanim nadejdzie ich czas. ;- )) Funkcja accumE po prostu przekształca jedną listę w drugą, przemierzając ją raz.

Będę musiał wyjaśnić ten sposób myślenia w dokumentacji.

" również chciałbym wiedzieć, jak można przejść o wyciąganie wiadomości z wielu gniazd - w tej chwili mam na pętli zdarzeń wewnątrz forever. Jako konkretny przykład tego, jak dodać drugie gniazdo (parę REQ/REP w slangu zeromq) do zapytania do aktualnego stanu IdMap wewnątrz licznika?"

Jeśli funkcja receive nie blokuje się, można ją wywołać dwukrotnie na różnych gniazdach

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

Jeśli blokuje, musisz użyć wątków, Zobacz też sekcję Obsługa wielu strumieni TCP w książce Real World Haskell. (Nie krępuj się zadać nowe pytanie na ten temat, ponieważ jest to poza zakresem tego.)

 21
Author: Heinrich Apfelmus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:09:20