2011-07-25 25 views
17

Đây là ví dụ về chương trình Haskell FRP sử dụng thư viện phản ứng chuối. Tôi chỉ mới bắt đầu cảm thấy theo cách của tôi với Haskell, và đặc biệt là không có khá đầu của tôi xung quanh những gì FRP có nghĩa là. Tôi thực sự đánh giá cao một số phê bình của mã bên dướiTôi có sử dụng chuối phản ứng đúng không?

{-# 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 

Có một ý chính tại đây: https://gist.github.com/1099712.

Tôi đặc biệt hoan nghênh bất kỳ nhận xét nào về việc sử dụng tiền tố "tốt" (Tôi không rõ chức năng này sẽ đi qua toàn bộ luồng sự kiện mỗi lần mặc dù tôi đoán là không).

Ngoài ra tôi muốn biết làm thế nào người ta sẽ đi về kéo tin nhắn từ nhiều ổ cắm - tại thời điểm này tôi có một vòng lặp sự kiện bên trong một mãi mãi. Như một ví dụ cụ thể về điều này làm thế nào tôi sẽ thêm ổ cắm thứ hai (một cặp REQ/REP trong ngôn ngữ zeromq) để truy vấn trạng thái hiện tại của IdMap bên trong bộ đếm?

Trả lời

21

(Tác giả của reactive-banana nói.)

Nhìn chung, mã của bạn có vẻ ổn với tôi. Tôi không thực sự hiểu tại sao bạn đang sử dụng chuối phản ứng ngay từ đầu, nhưng bạn sẽ có lý do. Điều đó nói rằng, nếu bạn đang tìm kiếm một cái gì đó như Node.js, hãy nhớ rằng chủ đề leightweight Haskell của make it unnecessary để sử dụng một kiến ​​trúc dựa trên sự kiện.

Phụ lục: Về cơ bản, chức năng lập trình phản hồi hữu ích khi bạn có nhiều đầu vào, trạng thái và đầu ra khác nhau phải hoạt động cùng với thời gian phù hợp (nghĩ GUI, hình động, âm thanh). Ngược lại, nó quá mức cần thiết khi bạn xử lý nhiều sự kiện độc lập cơ bản; chúng được xử lý tốt nhất với các chức năng thông thường và trạng thái không thường xuyên.


Liên quan đến câu hỏi cá nhân:

"Tôi đặc biệt muốn hoan nghênh bất kỳ bình luận xung quanh cho dù đây là một '' sử dụng tốt accumE, (tôi không rõ ràng về chức năng này sẽ đi qua toàn bộ sự kiện phát mỗi lần mặc dù tôi đoán là không). "

Có vẻ ổn với tôi. Như bạn đã đoán, hàm accumE thực sự là thời gian thực; nó sẽ chỉ lưu trữ giá trị tích lũy hiện tại.

Đánh giá từ dự đoán của bạn, dường như bạn nghĩ rằng bất cứ khi nào có sự kiện mới đến, nó sẽ di chuyển qua mạng như một con đom đóm. Trong khi điều này xảy ra trong nội bộ, đó là không phải cách bạn nên nghĩ về lập trình phản ứng chức năng. Thay vào đó, hình ảnh bên phải là: kết quả của fromAddHandler là danh sách đầy đủ các sự kiện đầu vào vì chúng sẽ xảy ra. Nói cách khác, bạn nên nghĩ rằng recvd chứa danh sách theo thứ tự của mỗi và mọi sự kiện từ tương lai. (Tất nhiên, vì lợi ích của sự tỉnh táo của bạn, bạn không nên cố gắng nhìn chúng trước khi thời gian của chúng đến. ;-)) Hàm accumE chỉ đơn giản biến đổi một danh sách thành một danh sách khác bằng cách duyệt qua nó một lần.

Tôi sẽ cần thực hiện cách suy nghĩ này rõ ràng hơn trong tài liệu.

"Ngoài ra tôi muốn biết làm thế nào người ta sẽ đi về kéo tin nhắn từ nhiều ổ cắm - tại thời điểm này tôi có trên vòng lặp sự kiện bên trong một mãi mãi.Như một ví dụ cụ thể của việc này thế nào tôi sẽ thêm ổ cắm thứ hai (một REQ/REP cặp theo cách nói zeromq) để truy vấn đến tình trạng hiện tại của idmap bên quầy?"

Nếu receive chức năng không ngăn chặn, bạn chỉ đơn giản là có thể gọi nó hai lần vào ổ cắm khác nhau

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

Nếu nó block, bạn sẽ cần phải sử dụng đề, xem thêm phần Handling Multiple TCP Streams trong cuốn sách Real World Haskell. (Hãy đặt một câu hỏi mới về vấn đề này, vì nó nằm ngoài phạm vi của điều này.)

+0

Cảm ơn Heinrich, lý do thứ tại FRP trông giống như một phù hợp tốt ở đây là nếu bạn có nhiều ổ cắm zeromq, họ có thể rất dễ dàng bắt đầu giống như sự kiện điều khiển đầu vào từ một GUI ... Vì vậy, tôi mặc dù tôi muốn đá lốp xe của một số ý tưởng mới :-) Bạn có nghĩ rằng nó có ý nghĩa hơn để làm điều này với một tiểu bang monad và chủ đề haskell? –

+0

@Ben Ford: Tôi đã thêm một nhận xét nhỏ vào câu trả lời. Tôi không biết những gì bạn muốn làm với các ổ cắm, vì vậy tôi không thể cho bạn biết liệu FRP là overkill cho mục đích của bạn. Về cơ bản, nếu mạng sự kiện của bạn sẽ không phát triển lớn hơn nhiều so với cái này với một 'E E' duy nhất và một số' filterE', thì nó hoàn toàn thanh lịch hơn mà không có FRP. –

Các vấn đề liên quan