这是一个使用反应香蕉库的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'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。
我特别欢迎任何有关这是否是“良好”使用accumE的评论,(我不清楚这个函数每次都会遍历整个事件流,虽然我猜不是)。
此外,我想知道如何从多个套接字中提取消息 - 目前我有一个永远的事件循环。作为一个具体的例子,我如何添加第二个套接字(在zeromq用语中的REQ / REP对)来查询计数器内部IdMap的当前状态?
答案 0 :(得分:21)
(reactive-banana发言的作者。)
总的来说,你的代码看起来很好。我实际上并不理解为什么你首先使用反应性香蕉,但你有理由。也就是说,如果你正在寻找类似Node.js的东西,请记住Haskell的轻量级线程make it unnecessary使用基于事件的架构。
附录:基本上,当您有各种不同的输入,状态和输出时,功能性反应式编程非常有用,这些输入,状态和输出必须与恰当的时序一起工作(想想GUI,动画,音频)。相比之下,当你处理许多基本上独立的事件时,这是过度的。这些最好用普通的功能和偶尔的状态来处理。
关于个别问题:
“我特别欢迎任何关于这是否是”好“使用accumE的评论,(我不清楚这个函数每次都会遍历整个事件流,虽然我猜不是)。”
对我来说很好看。正如您所猜测的那样,accumE
函数确实是实时的;它只会存储当前的累计值。
根据您的猜测,您似乎在想,每当有新事件发生时,它就会像萤火虫一样通过网络传播。虽然这确实发生在内部,但 相反,正确的图片是这样的:fromAddHandler
的结果是输入事件的完整列表,因为它们将发生。换句话说,您应该认为recvd
包含来自未来的每个事件的有序列表。 (当然,为了您自己的理智,您不应该在他们的时间到来之前尝试查看它们。;-))accumE
函数只需通过遍历一次就可以将一个列表转换为另一个列表。 / p>
我需要在文档中更清晰地思考这种方式。
“另外我想知道如何从多个套接字中提取消息 - 目前我已经在事件循环中永远存在。作为一个具体示例,我将如何添加第二个套接字(REQ / REP对用zeromq说法)来查询计数器内部IdMap的当前状态?“
如果receive
函数没有阻止,你只需在不同的套接字上调用它两次
linkSocketHandler s1 s2 runner1 runner2 = forever $ do
receive s1 [] >>= runner1 . fromString . C.unpack
receive s2 [] >>= runner2 . fromString . C.unpack
如果确实阻止,则需要使用线程,另请参阅Real World Haskell一书中的Handling Multiple TCP Streams部分。 (请随意提出一个新问题,因为它超出了本问题的范围。)