egd*_*try 6 irc monads state 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)
Run Code Online (Sandbox Code Playgroud)
因此,当我进入"已连接"状态时,我仍然会通过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
Run Code Online (Sandbox Code Playgroud)
该stateful管对应于你的状态的部分processMessage功能.它处理的初始化和验证,但通过重新推迟进一步的消息处理到下游级yield荷兰国际集团的msg.
然后,您可以Pipe yield使用for以下方法遍历每条消息:
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)
Run Code Online (Sandbox Code Playgroud)
现在你所需要的只是一个可以输入的ByteString行的来源processMessage.您可以使用以下内容Producer:
lines :: Producer ByteString IO ()
lines = do
bs <- liftIO (ByteString.getLine)
if ByteString.null bs
then return ()
else do
yield bs
lines
Run Code Online (Sandbox Code Playgroud)
然后,您可以连接lines到processMessage并运行它们:
runEffect (lines >-> processMessage) :: IO ()
Run Code Online (Sandbox Code Playgroud)
请注意,linesProducer不使用lazy IO.即使你使用strict ByteString模块它也会工作,但整个程序的行为仍然是懒惰的.
如果您想了解有关pipes工作原理的更多信息,可以阅读本pipes教程.