繁体   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