17

これは、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 の「良い」使用法であるかどうかについてのコメントを特に歓迎します (この関数が毎回イベント ストリーム全体をトラバースするかどうかはわかりませんが、そうではないと思います)。

また、複数のソケットからメッセージを取り込む方法を知りたいです-現時点では、永遠に1つのイベントループがあります。この具体的な例として、カウンター内の IdMap の現在の状態を照会するために、2 番目のソケット (zeromq 用語の REQ/REP ペア) を追加するにはどうすればよいでしょうか?

4

1 に答える 1

21

(リアクティブ バナナスピーキングの著者)

全体として、あなたのコードは私には問題ないように見えます。そもそも反応バナナを使用している理由は実際にはわかりませんが、理由はあるでしょう。とはいえ、Node.js のようなものを探している場合は、Haskell の軽量スレッドにより、イベントベースのアーキテクチャを使用する必要がないことを覚えておいてください。

補遺: 基本的に、関数型リアクティブ プログラミングは、適切なタイミング (GUI、アニメーション、オーディオなど) で連携する必要があるさまざまな入力、状態、および出力がある場合に役立ちます。対照的に、多くの本質的に独立したイベントを扱っている場合はやり過ぎです。これらは、通常の関数と不定期の状態で処理するのが最適です。


個別の質問について:

「これが accumE の「良い」使用法であるかどうかについてのコメントを特に歓迎します (この関数が毎回イベント ストリーム全体をトラバースするかどうかはわかりませんが、そうではないと思います)。」

私にはうまく見えます。ご想像のとおり、accumE関数は実際にリアルタイムです。現在の累積値のみが保存されます。

あなたの推測から判断すると、新しいイベントが発生するたびに、ホタルのようにネットワークを通過すると考えているようです。これは内部的に発生しますが、関数型リアクティブ プログラミングについて考える方法ではありません。むしろ、正しい図は次のとおりです。 の結果は、発生する入力イベントの完全なリストです。つまり、未来からのすべてのイベントの順序付きリストが含まれていると考える必要があります。(もちろん、あなた自身の正気を保つために、その時が来る前にそれらを見ようとするべきではありません。;-)) この関数は、1 つのリストを 1 回トラバースするだけで、1 つのリストを別のリストに変換します。fromAddHandlerrecvdaccumE

ドキュメントでは、この考え方をより明確にする必要があります。

「また、複数のソケットからメッセージを取り込む方法を知りたいです。現時点では、フォーエバー内でイベント ループを実行しています。具体的な例として、2 番目のソケット (REQ/REP ペア) を追加するにはどうすればよいですか?カウンタ内の IdMap の現在の状態を照会するには?"

receive関数がブロックされない場合は、異なるソケットで単純に 2 回呼び出すことができます。

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

ブロックする場合は、スレッドを使用する必要があります。Real World Haskell の本の「複数の TCP ストリームの処理」セクションも参照してください。(これについては、このトピックの範囲外であるため、お気軽に新しい質問をしてください。)

于 2011-07-25T14:16:35.393 に答える