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