Я правильно использую реактивно-банановый?

Здесь приведен пример программы Haskell FRP с использованием библиотеки реактивно-банановых. Я только начинаю ощущать свой путь с Haskell и особенно не совсем понимаю, что означает FRP. Я бы очень признателен за критику кода ниже

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

Здесь есть смысл: https://gist.github.com/1099712.

Я особенно приветствую любые комментарии относительно того, является ли это "хорошим" использованием accumE (я не понимаю, что эта функция будет проходить через весь поток событий каждый раз, хотя я предполагаю, что нет).

Также я хотел бы знать, как можно было бы тянуть сообщения из нескольких сокетов - на данный момент у меня есть один цикл событий внутри вечности. Как конкретный пример этого, как бы добавить второй сокет (пара REQ/REP в zeromq parlance) для запроса текущего состояния внутреннего счетчика IdMap?

Ответ 1

(Автор reactive-banana.)

В целом, ваш код выглядит хорошо для меня. Я действительно не понимаю, почему вы используете реактивный банан в первую очередь, но у вас будут причины. Тем не менее, если вы ищете что-то вроде Node.js, помните, что haskell leightweight threads не нужно использовать для использования архитектуры, основанной на событиях.

Приложение. В принципе, функциональное реактивное программирование полезно, когда у вас есть множество различных входов, состояний и вывода, которые должны работать вместе с правильным временем (подумайте о графиках, анимации, аудио). Напротив, он переполняется, когда вы имеете дело со многими существенно независимыми событиями; они лучше всего обрабатываются обычными функциями и случайным состоянием.


Относительно индивидуальных вопросов:

"Я особенно приветствую любые комментарии относительно того, является ли это" хорошим "использованием accumE (я не понимаю, что эта функция будет обрабатывать весь поток событий каждый раз, хотя я предполагаю, что нет)".

Выглядит хорошо. Как вы уже догадались, функция accumE действительно в режиме реального времени; он сохранит только текущее накопленное значение.

Судя по вашей догадке, вы, кажется, думаете, что всякий раз, когда приходит новое событие, он будет путешествовать по сети, как светлячок. Хотя это происходит внутри страны, вы не должны думать о функциональном реактивном программировании. Скорее, правильная картина такова: результат fromAddHandler - это полный список входных событий по мере их возникновения. Другими словами, вы должны думать, что recvd содержит упорядоченный список каждого события из будущего. (Конечно, в интересах вашего собственного здравомыслия, вы не должны пытаться смотреть на них до того, как их время наступит.;-)) Функция accumE просто преобразует один список в другой, пройдя его один раз.

Мне нужно будет сделать этот способ мышления более ясным в документации.

"Также я хотел бы знать, как можно было бы тянуть сообщения из нескольких сокетов - в тот момент, когда я нахожусь в цикле событий внутри вечно. В качестве конкретного примера этого, как бы добавить второй сокет (REQ/REP в zeromq parlance), чтобы запросить текущее состояние внутреннего счетчика IdMap?"

Если функция receive не блокируется, вы можете просто дважды ее вызвать в разных сокетах

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

Если он блокируется, вам нужно будет использовать потоки, см. также раздел Обработка нескольких потоков TCP в книге Real World Haskell. (Не стесняйтесь задавать новый вопрос по этому вопросу, поскольку он выходит за рамки этого.)