簡體   English   中英

什么是在Haskell中處理延遲輸入通道的慣用方法

[英]What's an idiomatic way of handling a lazy input channel in Haskell

我正在實現一個IRC機器人,因為我通過使用OpenSSL.Session連接SSL,我使用lazyRead函數從套接字讀取數據。 在連接的初始階段,我需要按順序執行幾項操作:缺口協商,缺口服務器識別,加入渠道等)因此涉及一些狀態。 現在我想出了以下內容:

data ConnectionState = Initial | NickIdentification | Connected

listen :: SSL.SSL -> IO ()
listen ssl = do
  lines <- BL.lines `fmap` SSL.lazyRead ssl
  evalStateT (mapM_ (processLine ssl) lines) Initial

processLine :: SSL.SSL -> BL.ByteString -> StateT ConnectionState IO ()
processLine ssl line = do case message of
                            Just a -> processMessage ssl a
                            Nothing -> return ()
  where message = IRC.decode $ BL.toStrict line

processMessage :: SSL.SSL -> IRC.Message -> StateT ConnectionState IO ()
processMessage ssl m = do
    state <- S.get
    case state of
      Initial -> when (IRC.msg_command m == "376") $ do
        liftIO $ putStrLn "connected!"
        liftIO $ privmsg ssl "NickServ" ("identify " ++ nick_password)
        S.put NickIdentification
      NickIdentification -> do
        when (identified m) $ do
          liftIO $ putStrLn "identified!"
          liftIO $ joinChannel ssl chan
          S.put Connected
      Connected -> return ()
    liftIO $ print m
    when (IRC.msg_command m == "PING") $ (liftIO . pong . mconcat . map show) (IRC.msg_params m)

因此,當我進入“已連接”狀態時,我仍然會通過case語句,即使它只是真正需要初始化連接。 另一個問題是添加嵌套的StateT會非常痛苦。

另一種方法是用自定義的東西替換mapM ,直到我們連接然后再開始另一個循環。 這需要跟蹤列表中剩下的內容或再次調用SSL.lazyRead (這不是太糟糕)。

另一種解決方案是將剩余的行列表保持在狀態,並在需要時繪制與getLine類似的行。

在這種情況下,做什么更好? Haskell的懶惰是否會使我們在狀態停止更新后直接進入Connected案例或者case總是嚴格的?

您可以使用Pipe從類型pipes 訣竅是,您可以隱式地在Pipe的控制流中對狀態進行編碼,而不是創建狀態機和轉換函數。

這是Pipe樣子:

stateful :: Pipe ByteString ByteString IO r
stateful = do
    msg <- await
    if (IRC.msg_command msg == "376")
        then do
            liftIO $ putStrLn "connected!"
            liftIO $ privmsg ssl "NickServ" ("identify " ++ nick_password)
            yield msg
            nick
        else stateful

nick :: Pipe ByteString ByteString IO r
nick = do
    msg <- await
    if identified msg
        then do
            liftIO $ putStrLn "identified!"
            liftIO $ joinChannel ssl chan
            yield msg
            cat  -- Forward the remaining input to output indefinitely 
        else nick

stateful管道對應於processMessage函數的有狀態部分。 它處理的初始化和驗證,但通過重新推遲進一步的消息處理到下游階段yield荷蘭國際集團的msg

然后,您可以使用for循環遍歷此Pipe yield的每條消息:

processMessage :: Consumer ByteString IO r
processMessage = for stateful $ \msg -> do
    liftIO $ print m
    when (IRC.msg_command m == "PING") $ (liftIO . pong . mconcat . map show) (IRC.msg_params m)

現在您只需要一個ByteString行的源來提供給processMessage 您可以使用以下Producer

lines :: Producer ByteString IO ()
lines = do
    bs <- liftIO (ByteString.getLine)
    if ByteString.null bs
        then return ()
        else do
            yield bs
            lines

然后,您可以將lines連接到processMessage並運行它們:

runEffect (lines >-> processMessage) :: IO ()

請注意,生產者lines不使用惰性IO 即使你使用嚴格的ByteString模塊它也會工作,但整個程序的行為仍然是懶惰的。

如果您想了解有關pipes如何工作的更多信息,可以閱讀pipes教程

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM