2011-07-25 31 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的情況下它會更加優雅。 –