使用状态从 wiki.haskell.org 扩展 IRC 机器人
Extending the IRC bot from wiki.haskell.org with state
问题
我正在尝试从 https://wiki.haskell.org/Roll_your_own_IRC_bot 扩展 IRC 机器人,每次机器人 post 在其连接的频道中发送消息时都会更新一些状态。
特点是:每次在IRC频道中发出命令!last said
,bot都会回复一个时间戳。为了支持这一点,privmsg
函数需要在每次调用时使用新的时间戳更新机器人的状态——特别是 lastPosted
记录。
到目前为止工作
我从 Haskell wiki 页面底部获取代码(使用 ReaderT 访问有关机器人环境的信息)并尝试更改 Reader T 代表状态转换器 (StateT)。结果如下,如您所见,我并没有走得太远。
import Data.List
import Network
import System.IO
import System.Exit
import System.Time
import Control.Arrow
import Control.Monad.State
import Control.Exception
import Text.Printf
server = "irc.freenode.org"
port = 6667
chan = "#testbot-test"
nick = "testbottest"
-- The 'Net' monad, a wrapper over IO, carrying the bot's immutable state.
type Net = StateT Bot IO
data Bot = Bot { socket :: Handle, lastPosted :: ClockTime }
-- Set up actions to run on start and end, and run the main loop
main :: IO ()
main = bracket connect disconnect loop
where
disconnect = hClose . socket
loop st = runStateT run st
-- Connect to the server and return the initial bot state
connect :: IO Bot
connect = notify $ do
h <- connectTo server (PortNumber (fromIntegral port))
t <- getClockTime
hSetBuffering h NoBuffering
return (Bot h t)
where
notify a = bracket_
(printf "Connecting to %s ... " server >> hFlush stdout)
(putStrLn "done.")
a
-- We're in the Net monad now, so we've connected successfully
-- Join a channel, and start processing commands
run :: Net ()
run = do
write "NICK" nick
write "USER" (nick ++ " 0 * :test bot")
write "JOIN" chan
gets socket >>= listen
-- Process each line from the server
listen :: Handle -> Net ()
listen h = forever $ do
s <- init `fmap` liftIO (hGetLine h)
liftIO (putStrLn s)
if ping s then pong s else eval (clean s)
where
forever a = a >> forever a
clean = drop 1 . dropWhile (/= ':') . drop 1
ping x = "PING :" `isPrefixOf` x
pong x = write "PONG" (':' : drop 6 x)
-- Dispatch a command
eval :: String -> Net ()
eval "!quit" = write "QUIT" ":Exiting" >> liftIO (exitWith ExitSuccess)
-- Posting when something was last posted shouldn't count as last posted.
eval "!last said" = getLastPosted >>= (\t -> write "PRIVMSG" (chan ++ " :" ++ t))
eval x | "!id " `isPrefixOf` x = privmsg (drop 4 x)
eval _ = return () -- ignore everything else
getLastPosted :: Net String
getLastPosted = do
t <- gets lastPosted
return $ show t
-- Send a privmsg to the current chan + server
privmsg :: String -> Net ()
privmsg s = write "PRIVMSG" (chan ++ " :" ++ s)
-- Send a message out to the server we're currently connected to
write :: String -> String -> Net ()
write s t = do
h <- gets socket
liftIO $ hPrintf h "%s %s\r\n" s t
liftIO $ printf "> %s %s\n" s t
探索其他支持途径
花了几天时间阅读 ReaderT、StateT 和他们的非变形金刚朋友 Reader 和 State,
检查 Stack Overflow 是否有任何有类似问题的人,但唯一的其他 IRC 机器人问题将套接字作为每个需要它的函数的参数(而不是使用 ReaderT ),
Tweeted Don S. 维基页面的原作者
在 Haskell IRC 频道中提问。
问题
如何将 Haskell wiki IRC bot 扩展为 post 一条消息,其中包含最后一条消息的日期和时间戳 posted?最好使用像 ReaderT 这样的抽象(只允许可变状态)而不是在函数参数中传递状态。
我通过简单地在你的 main 中的 loop
的定义中添加一个 >> return ()
来编译你的代码:
main :: IO ()
main = bracket connect disconnect loop
where
disconnect = hClose . socket
loop st = (runStateT run st) >> return ()
这实际上忽略了 runStateT
的 return 值。以下是 runState/runStateT 的所有变体:
runStateT
- return 最终状态和 returned 值
evalStateT
- return 只有最终值
execStateT
- return只有最终状态
您对 loop
的原始定义是 returning 一对(来自 runStateT),并且这没有类型检查,因为 main
想要一个 return 的计算只是 ()
.
要更新 lastPosted
字段,请考虑将此添加到 eval
函数,该函数在机器人发送消息 !update time
:
时触发
eval "!update time"
= do t <- liftIO getClockTime
bot <- get
put (bot { lastPosted = t })
我们需要 liftIO getClockTime
因为我们在 Net
monad 中操作。
然后我们 get
旧状态和 put
更新状态。您可以在 Net
monad.
中任何您想更新 lastPosted 时间的地方添加此逻辑
完整代码位于:http://lpaste.net/142931
问题
我正在尝试从 https://wiki.haskell.org/Roll_your_own_IRC_bot 扩展 IRC 机器人,每次机器人 post 在其连接的频道中发送消息时都会更新一些状态。
特点是:每次在IRC频道中发出命令!last said
,bot都会回复一个时间戳。为了支持这一点,privmsg
函数需要在每次调用时使用新的时间戳更新机器人的状态——特别是 lastPosted
记录。
到目前为止工作
我从 Haskell wiki 页面底部获取代码(使用 ReaderT 访问有关机器人环境的信息)并尝试更改 Reader T 代表状态转换器 (StateT)。结果如下,如您所见,我并没有走得太远。
import Data.List
import Network
import System.IO
import System.Exit
import System.Time
import Control.Arrow
import Control.Monad.State
import Control.Exception
import Text.Printf
server = "irc.freenode.org"
port = 6667
chan = "#testbot-test"
nick = "testbottest"
-- The 'Net' monad, a wrapper over IO, carrying the bot's immutable state.
type Net = StateT Bot IO
data Bot = Bot { socket :: Handle, lastPosted :: ClockTime }
-- Set up actions to run on start and end, and run the main loop
main :: IO ()
main = bracket connect disconnect loop
where
disconnect = hClose . socket
loop st = runStateT run st
-- Connect to the server and return the initial bot state
connect :: IO Bot
connect = notify $ do
h <- connectTo server (PortNumber (fromIntegral port))
t <- getClockTime
hSetBuffering h NoBuffering
return (Bot h t)
where
notify a = bracket_
(printf "Connecting to %s ... " server >> hFlush stdout)
(putStrLn "done.")
a
-- We're in the Net monad now, so we've connected successfully
-- Join a channel, and start processing commands
run :: Net ()
run = do
write "NICK" nick
write "USER" (nick ++ " 0 * :test bot")
write "JOIN" chan
gets socket >>= listen
-- Process each line from the server
listen :: Handle -> Net ()
listen h = forever $ do
s <- init `fmap` liftIO (hGetLine h)
liftIO (putStrLn s)
if ping s then pong s else eval (clean s)
where
forever a = a >> forever a
clean = drop 1 . dropWhile (/= ':') . drop 1
ping x = "PING :" `isPrefixOf` x
pong x = write "PONG" (':' : drop 6 x)
-- Dispatch a command
eval :: String -> Net ()
eval "!quit" = write "QUIT" ":Exiting" >> liftIO (exitWith ExitSuccess)
-- Posting when something was last posted shouldn't count as last posted.
eval "!last said" = getLastPosted >>= (\t -> write "PRIVMSG" (chan ++ " :" ++ t))
eval x | "!id " `isPrefixOf` x = privmsg (drop 4 x)
eval _ = return () -- ignore everything else
getLastPosted :: Net String
getLastPosted = do
t <- gets lastPosted
return $ show t
-- Send a privmsg to the current chan + server
privmsg :: String -> Net ()
privmsg s = write "PRIVMSG" (chan ++ " :" ++ s)
-- Send a message out to the server we're currently connected to
write :: String -> String -> Net ()
write s t = do
h <- gets socket
liftIO $ hPrintf h "%s %s\r\n" s t
liftIO $ printf "> %s %s\n" s t
探索其他支持途径
花了几天时间阅读 ReaderT、StateT 和他们的非变形金刚朋友 Reader 和 State,
检查 Stack Overflow 是否有任何有类似问题的人,但唯一的其他 IRC 机器人问题将套接字作为每个需要它的函数的参数(而不是使用 ReaderT ),
Tweeted Don S. 维基页面的原作者
在 Haskell IRC 频道中提问。
问题
如何将 Haskell wiki IRC bot 扩展为 post 一条消息,其中包含最后一条消息的日期和时间戳 posted?最好使用像 ReaderT 这样的抽象(只允许可变状态)而不是在函数参数中传递状态。
我通过简单地在你的 main 中的 loop
的定义中添加一个 >> return ()
来编译你的代码:
main :: IO ()
main = bracket connect disconnect loop
where
disconnect = hClose . socket
loop st = (runStateT run st) >> return ()
这实际上忽略了 runStateT
的 return 值。以下是 runState/runStateT 的所有变体:
runStateT
- return 最终状态和 returned 值evalStateT
- return 只有最终值execStateT
- return只有最终状态
您对 loop
的原始定义是 returning 一对(来自 runStateT),并且这没有类型检查,因为 main
想要一个 return 的计算只是 ()
.
要更新 lastPosted
字段,请考虑将此添加到 eval
函数,该函数在机器人发送消息 !update time
:
eval "!update time"
= do t <- liftIO getClockTime
bot <- get
put (bot { lastPosted = t })
我们需要 liftIO getClockTime
因为我们在 Net
monad 中操作。
然后我们 get
旧状态和 put
更新状态。您可以在 Net
monad.
完整代码位于:http://lpaste.net/142931