Я использую реактивный банан правильно? - PullRequest
17 голосов
/ 25 июля 2011

Вот пример программы на Haskell FRP, использующей библиотеку реактивного банана. Я только начинаю чувствовать себя с Хаскеллом, и особенно не совсем понял, что значит 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'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

Здесь есть суть: https://gist.github.com/1099712.

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

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

1 Ответ

21 голосов
/ 25 июля 2011

(Автор реактивный-банан говорит.)

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

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


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

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

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

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

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

"Также мне хотелось бы знать, как можно было бы извлекать сообщения из нескольких сокетов - на данный моментУ меня есть цикл обработки событий внутри навсегда. Как конкретный пример этого, как бы я добавить второй сокет (пара REQ / REP на языке zeromq) для запроса к текущему состоянию внутреннего счетчика 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.(Не стесняйтесь задавать новый вопрос по этому вопросу, поскольку он выходит за рамки этого.)

...