我使用反应香蕉对吗?
下面是一个使用reactive-banana 库的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 的当前状态?
Here's an example Haskell FRP program using the reactive-banana library. I'm only just starting to feel my way with Haskell, and especially haven't quite got my head around what FRP means. I'd really appreciate some critique of the code below
{-# 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 <gt; 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
There's a gist here: https://gist.github.com/1099712.
I'd particularly welcome any comments around whether this is a "good" use of accumE, (I'm unclear of this function will traverse the whole event stream each time although I'm guessing not).
Also I'd like to know how one would go about pulling in messages from multiple sockets - at the moment I have one event loop inside a forever. As a concrete example of this how would I add second socket (a REQ/REP pair in zeromq parlance) to query to the current state of the IdMap inside counter?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
(reactive-banana 的作者。)
总体而言,您的代码对我来说看起来不错。我实际上不明白你为什么要使用reactive-banana 首先,但你会有你的理由。也就是说,如果您正在寻找类似 Node.js 的东西,请记住 Haskell 的轻量级线程 使得没有必要使用基于事件的架构。
附录:基本上,当您有各种不同的输入、状态和输出,并且必须在正确的时机(例如 GUI、动画、音频)一起工作时,函数反应式编程非常有用。相反,当你处理许多本质上独立的事件时,它就显得有些过分了。这些最好用普通函数和偶尔的状态来处理。
关于个别问题:
对我来说看起来不错。正如您所猜测的,accumE 函数确实是实时的;它只会存储当前的累计值。
从你的猜测来看,你似乎在想,每当有新事件到来时,它都会像萤火虫一样在网络中传播。虽然这种情况确实发生在内部,但这并不是您应该如何思考函数式响应式编程。相反,正确的图片是这样的:
fromAddHandler
的结果是输入事件的完整列表因为它们将发生。换句话说,您应该认为recvd
包含未来每个事件的有序列表。 (当然,为了您自己的理智,您不应该在时机到来之前尝试查看它们。;-))accumE 函数只是通过遍历一个列表将其转换为另一个列表一次。我需要在文档中使这种思维方式更加清晰。
如果
receive
函数没有阻塞,您只需在不同的套接字上调用它两次即可。如果它确实阻塞,您将需要使用线程,另请参阅 处理多个 TCP 流。 (请随意就此提出一个新问题,因为它超出了本问题的范围。)
(Author of reactive-banana speaking.)
Overall, your code looks fine to me. I don't actually understand why you are using reactive-banana in the first place, but you'll have your reasons. That said, if you are looking for something like Node.js, remember that Haskell's leightweight threads make it unnecessary to use an event-based architecture.
Addendum: Basically, functional reactive programming is useful when you have a variety of different inputs, states and output that must work together with just the right timing (think GUIs, animations, audio). In contrast, it's overkill when you are dealing with many essentially independent events; these are best handled with ordinary functions and the occasional state.
Concerning the individual questions:
Looks fine to me. As you guessed, the
accumE
function is indeed real-time; it will only store the current accumulated value.Judging from your guess, you seem to be thinking that whenever a new event comes in, it will travel through the network like a firefly. While this does happen internally, it is not how you should think about functional reactive programming. Rather, the right picture is this: the result of
fromAddHandler
is the complete list of input events as they will happen. In other words, you should think thatrecvd
contains the ordered list of each and every event from the future. (Of course, in the interest of your own sanity, you shouldn't try to look at them before their time has come. ;-)) TheaccumE
function simply transforms one list into another by traversing it once.I will need to make this way of thinking more clear in the documentation.
If the
receive
function does not block, you can simply call it twice on different socketsIf it does block, you will need to use threads, see also the section Handling Multiple TCP Streams in the book Real World Haskell. (Feel free to ask a new question on this, as it is outside the scope of this one.)