Я использую реактивный банан правильно?
Вот пример программы на 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 ответ
(Автор реактивно-банановой речи.)
В целом, ваш код выглядит нормально для меня. На самом деле я не понимаю, почему вы используете реактивный банан, но у вас будут свои причины. Тем не менее, если вы ищете что-то вроде 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". (Не стесняйтесь задавать новый вопрос по этому вопросу, поскольку он выходит за рамки этого.)