2011-07-25 35 views
17

下面是使用反应式香蕉库的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的当前状态?

回答

21

(作者reactive-banana讲的。)

总体而言,你的代码看起来好像没什么问题。我真的不明白你为什么首先使用反应性香蕉,但你会有你的理由。也就是说,如果您正在寻找像Node.js这样的东西,请记住Haskell的轻量级线程make it unnecessary使用基于事件的体系结构。

附录:基本上,当您需要各种不同的输入,状态和输出时,功能反应式编程很有用,这些输入,状态和输出必须在恰当的时机(想象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 

如果它块,你将需要使用线程,也是部分看到Handling Multiple TCP Streams在书中真实世界哈斯克尔(随意问了一个新问题在此,因为它不在此范围之内。)

+0

感谢海因里希,原因是在FRP看起来很合适,如果你有很多zeromq套接字,他们可以非常容易地开始像一个GUI的事件驱动输入...所以我尽管我会踢一些新的想法轮胎:-) 你认为用State monad和haskell线程做这件事更有意义吗? –

+0

@Ben Ford:我在答案中加了一个小小的评论。我不知道你想用套接字做什么,所以我不能告诉你FRP是否为你的目的矫枉过正。基本上,如果你的事件网络不会比单独的'accumE'和一些'filterE'大得多,那么在没有FRP的情况下它会更加优雅。 –