我使用反应香蕉对吗?

发布于 2024-11-25 21:53:17 字数 2475 浏览 4 评论 0原文

下面是一个使用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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

撩动你心 2024-12-02 21:53:17

reactive-banana 的作者。)

总体而言,您的代码对我来说看起来不错。我实际上不明白你为什么要使用reactive-banana 首先,但你会有你的理由。也就是说,如果您正在寻找类似 Node.js 的东西,请记住 Haskell 的轻量级线程 使得没有必要使用基于事件的架构。

附录:基本上,当您有各种不同的输入、状态和输出,并且必须在正确的时机(例如 GUI、动画、音频)一起工作时,函数反应式编程非常有用。相反,当你处理许多本质上独立的事件时,它就显得有些过分了。这些最好用普通函数和偶尔的状态来处理。


关于个别问题:

“我特别欢迎任何关于这是否是 accumE 的“良好”使用的评论,(我不清楚这个函数每次都会遍历整个事件流,尽管我猜不会)。”

对我来说看起来不错。正如您所猜测的,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 流。 (请随意就此提出一个新问题,因为它超出了本问题的范围。)

(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:

"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)."

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 that recvd 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. ;-)) The accumE 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.

"Also I'd like to know how one would go about pulling in messages from multiple sockets - at the moment I have on 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?"

If the receive function does not block, you can simply call it twice on different sockets

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

If 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.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文