我正在尝试从https://wiki.haskell.org/Roll_your_own_IRC_bot扩展IRC机器人,并且每次机器人在其连接的通道中发布消息时都会更新。
该功能是:每次在IRC频道中发出命令!last said
时,机器人都应该使用时间戳进行响应。为了支持这一点,privmsg
函数需要更新机器人的状态 - 特别是lastPosted
记录 - 每次调用时都会使用新的时间戳。
我从Haskell维基页面底部(使用ReaderT访问有关机器人环境的信息)获取代码,并尝试更改状态转换器(StateT)的ReaderT。结果如下,你可以看到,我没有走得太远。
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 and State,
检查具有类似问题的任何人的Stack Overflow,但唯一的其他IRC机器人问题将套接字作为参数连接到需要它的每个函数(而不是使用ReaderT),
Tweeted Don S.维基页面的原作者
在Haskell IRC频道中提问。
如何扩展Haskell wiki IRC bot以发布消息,其中包含上次发布消息的日期和时间戳?最好使用像ReaderT这样的抽象(只允许可变状态),而不是在函数参数中传递状态。
答案 0 :(得分:2)
我只需在您的主要内容>> return ()
的定义中添加loop
即可编译您的代码:
main :: IO ()
main = bracket connect disconnect loop
where
disconnect = hClose . socket
loop st = (runStateT run st) >> return ()
这实际上忽略了runStateT
的返回值。以下是runState / runStateT的所有变体:
runStateT
- 返回最终状态和返回值evalStateT
- 仅返回最终值execStateT
- 仅返回最终状态您loop
的原始定义是返回一对(来自runStateT),并且这不会进行类型检查,因为main
想要一个只返回()
的计算。
要更新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