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?
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ń:
Jak dla mnie wygląda dobrze. Jak się domyślasz, funkcja"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)."
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.)
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