【问题标题】:Am I using reactive-banana right?我使用的是反应香蕉吗?
【发布时间】:2011-10-12 08:32:06
【问题描述】:

这是一个使用响应式香蕉库的 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 的“好”用法的 cmets,(我不清楚这个函数每次都会遍历整个事件流,尽管我猜不是)。

我还想知道如何从多个套接字中提取消息 - 目前我在一个永远的事件循环内有一个事件循环。作为一个具体的例子,我将如何添加第二个套接字(zeromq 用语中的 REQ/REP 对)来查询计数器内 IdMap 的当前状态?

【问题讨论】:

    标签: haskell zeromq reactive-programming frp reactive-banana


    【解决方案1】:

    reactive-banana的作者发言。)

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

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


    关于个别问题:

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

    我觉得不错。如您所料,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
    

    如果它确实阻塞,您将需要使用线程,另请参阅 Real World Haskell 一书中的Handling Multiple TCP Streams 部分。 (请随时就此提出新问题,因为它超出了本问题的范围。)

    【讨论】:

    • 感谢 Heinrich,FRP 在这里看起来很合适的原因是,如果你有许多 zeromq 套接字,它们很容易开始类似于来自 GUI 的事件驱动输入......所以我虽然我会厌倦一些新想法 :-) 你认为用 State monad 和 haskell 线程来做这件事更有意义吗?
    • @Ben Ford:我在答案中添加了一个小注释。我不知道你想用套接字做什么,所以我不能告诉你 FRP 是否对你的目的来说太过分了。基本上,如果您的活动网络不会比只有一个 accumE 和一些 filterE 的活动网络大得多,那么没有 FRP 会更优雅。
    猜你喜欢
    • 2013-06-20
    • 2017-11-22
    • 2011-09-25
    • 2013-11-09
    • 1970-01-01
    • 2012-06-08
    • 2012-10-15
    • 2011-12-12
    • 2014-06-17
    相关资源
    最近更新 更多