[英]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.