Haskell

Recent site activity

XMPP bot programming

Interesting Resources

posted ‎‎Jan 22, 2009 12:25 PM‎‎ by Wei Hu   [ updated ‎‎Apr 2, 2009 8:49 AM‎‎ ]

Design

posted ‎‎Oct 4, 2008 1:39 PM‎‎ by Wei Hu   [ updated ‎‎Oct 5, 2008 2:28 PM‎‎ ]

Initially, I run each plugin module in a new thread, and duplicate a channel for every thread. The main thread is responsible for receiving incoming messages and placing the messages on the channel. Every thread then gets the message from its duplicated channel. I find this design a little bit more complicated, and I'm going to rewrite it using FRP.

Also, I'd like my plugin architecture to resemble lambdabot's as much as possible, so that I can port lambdabot's plugins easily to mine.

Just found out that jabber.org now requires TLS-compatible clients. I managed to figure out how to use hsgnutls to talk to the server. But, to authenticate the user, we need to support SASL and there's no binding for SASL libraries yet. For now, I only support the PLAIN mechanism.

Functional Reactive Programming

posted ‎‎Oct 2, 2008 1:08 PM‎‎ by Wei Hu   [ updated ‎‎May 5, 2009 1:04 PM‎‎ ]

Conal Elliott's blog archives. He has made quite a few FRP libs and apps, among which Reactive seems to be his latest effort. Its successor derived from his 08 paper has yet to be released. Another library: Fregl (code).

FRP provides a first class encapsulation of the infinite time-line of varying values. As such, it replaces many of the uses of imperative programming. It's been primarily used in functional UI and real time systems. There are more understandable examples using FRP to implement network servers.

From my superficial understanding, you first call mkEvent in a IO thread to make a polymorphic pair of (Event a, Sink a). Then, you pass your sink to a function, that receives a stream of data and feeds the stream into the sink. Each value sent to the sink becomes an occurrence of the event. You can call subscribe to assign a listener to the event. Finally, to run the event from your IO thread, use runE with fmap.

EDIT: See also Luke Palmer's discussion and code. And the blog series over here.

The best tutorials I found on FRP are at this blog (in F# though).

Lambdabot

posted ‎‎Aug 26, 2008 11:25 PM‎‎ by Wei Hu   [ updated ‎‎Oct 4, 2008 1:38 PM‎‎ ]

Roll your own IRC bot has helped me a lot in understanding lambdabot. I'm going to jot down some lessons I learned while reading the vanilla code as well as the XMPP port. For documentation, see the files COMMENTARY and STYLE. One thing I hate about lambdabot-xmpp is that it uses some very old packages. It takes a little time to get it to build under ghc-6.8. I also removed some plugins as a quick and dirty fix. Even then I didn't figure out how to make it join a XMPP conference room.

The best way to learn about lambdabot is to learn about the plugins. See Hello and Dummy. 

The work flow

main (Main.hs) passes modulesInfo to main'. modulesInfo (Modules.hs) is a list of plugins loaded by modules (Plugin.hs). Every plugin module is an instance of the Module class (Lambdabot.hs) defined in files under Plugin/.
main' (LMain.hs) simply calls runIrc.
runIrc (Lambdabot.hs) switches from the IO monad to mainLoop in the LB monad, by calling evalLB.

Differences

The vanilla version's runIrc does the initialization and sets up a TCP connection to the server, but no message is sent. mainLoop also seems to be a dummy function. All the real work is done by plugins. To add a plugin, you write a Haskell file under Plugin/, and add it to the list in modulesInfo. Some Template Haskell trick will parse this list and call ircInstallModule (Lambdabot.hs) to install each module in the list. The modules Base, State, System, OfflineRC are recommended to be included first. The real IRC work is done in online (Plugin/IRC.hs).

online first calls ircSignOn. It then forks a thread to read the incoming messages indefinitely. When a message is received, the function readerLoop calls forkLB, the wrapper for forkIO, to fork a thread to process the incoming message. To do so, it first decodeMessage, then passes it to received (LMain.hs). received calls allCallbacks, which calls every registered callbacks, among which Plugin/Base.hs is the most important one.

The Base module's moduleInit registers a bunch of callbacks for different commands. It handles a few commands itself, like PING, JOIN, QUIT, etc. All chat messages, group or personal, are prefixed with PRIVMSG and handled by doPRIVMSG, which dispatches commands to registered modules.

The xmpp version (which is older) does initialization and connects to the server (only the connection, no message sent) in mainLoop. A reader loop and a writer loop are forked. Depending on config, they can be either IRC.readerLoop + IRC.writerLoop, or Xmpp.readerLoop + Xmpp.writerLoop.

Template Haskell

We look at Plugin/Hello.hs as an example to understand how to add a plugin.

Plugin/Hello.hs

{-# LANGUAGE TemplateHaskell, MultiParamTypeClasses #-}

module Plugin.Hello where

import Plugin

$(plugin "Hello")

instance Module HelloModule () where
    moduleCmds _  = ["hello","goodbye"]
    moduleHelp _  _ = "hello/goodbye <arg>. Simplest possible plugin"
    process_ _ xs _ = return ["Hello world. " ++ xs]


$(plugin "Hello") is evaluated at COMPILE TIME, and substituted with returned value that represents ordinary Haskell code. Plugin.hs has the definition of plugin:

plugin :: String -> Q [Dec]
plugin n = sequence [typedec, fundec]
 where
    fundec = funD themod [clause [] (normalB ([| MODULE $(conE mod) |]) ) []]
    -- typedec = newtypeD (cxt []) mod [] (normalC mod [strictType notStrict [t|()|]]) []                                    
    typedec = dataD (cxt []) mod [] [normalC mod []] []
    themod = mkName "theModule"
    mod = mkName $ n ++ "Module"

Type Q represents Quotation Monad. In Template Haskell, ordinary algebraic data types represent Haskell program fragments. These types are modeled after Haskell language syntax and represents AST (abstract syntax tree) of corresponding Haskell code. There is an Exp type to represent Haskell expressions, Pat – for patterns, Lit – for literals, Dec – for declarations, Type – for data types and so on. The TH definitions are available in Language.Haskell.TH. These types refer to each other according to rules of Haskell syntax, so using them you can construct values representing any possible Haskell program fragments.

In order to make debugging Template Haskell programs easier, compiler supports flag -ddump-splices, which shows the expansion of all top-level splices as they happen. Also, you can run computations in Q monad programmatically with help of “runQ::Q a->IO a”. The results can be printed in form of AST as they implement the Show class, or in the form of Haskell code with the pretty printer function pprint.

We load Plugin.hs in ghci to see what $(plugin "Hello") expands to

*Plugin> (runQ $ plugin "Hello") >>= putStrLn . pprint

data HelloModule = HelloModule
theModule = Lambdabot.MODULE HelloModule

We could guess pretty much most meaning of plugin. The quotation brackets [| ... |] are worth a little discussion – they parse ordinary Haskell code and returns Exp structure representing it. MODULE in the bracket resolves to Lambdabot.MODULE.

A plugin must implement the MODULE class (class Module m s | m -> s). HelloModule doesn't need state, hence instance Module HelloModule ().

We also need to tell lambdabot to load our new module, in Modules.hs.

Modules.hs

...
import qualified Plugin.Hello

modulesInfo :: (LB (), [String])
modulesInfo = $(modules $ nub
                    [ "Base"
                    , "State"
                    , "System"
                    , "OfflineRC"

                    , "Hello"
                    ...
                    ])

Plugin.hs has the definition of modules:

modules :: [String] -> Q Exp
modules xs = [| ($install, $names) |]
 where
    names = listE $ map (stringE . map toLower) xs
    install = [| sequence_ $(listE $ map instalify xs) |]
    instalify x = let mod = varE $ mkName $ concat $ ["Plugin.", x, ".theModule"]
                      low = stringE $ map toLower x
                  in [| ircInstallModule $mod $low |]

Let's expand it:

*Plugin> (runQ $ modules ["Base", "Hello"]) >>= putStrLn . pprint

(Control.Monad.sequence_ [Lambdabot.ircInstallModule Plugin.Base.theModule "base",
                          Lambdabot.ircInstallModule Plugin.Hello.theModule "hello"],
 ["base", "hello"])


hsxmpp

posted ‎‎Aug 20, 2008 4:08 PM‎‎ by Wei Hu   [ updated ‎‎Oct 4, 2008 1:37 PM‎‎ ]

XMPP is a suite of open protocols including core and extensions. The Multi-User Chat protocol is used for conference rooms. Since I wanted to practice my Haskell skills, I played a little with hsxmpp. My modified repository is here. hsxmpp is small enough that I could understand it partially and learn from it.
 
Most side-effectful libraries provide the main interface as a monad, such as XMonad, Yi, and H. hsxmpp is no exception. A simple program demonstrating its usage is in the doc. Often we find ourselves wanting side effects while in a monad, such as modifying states, or writing out debug information. The hsxmpp author foresaw this need and made its XMPP monad instance of MonadIO, so that we can perform IO actions in XMPP using liftIO.
 
XMPP is XML-based. hsxmpp automatically dumps all communications to the screen, making debugging easier.
 
The basic workflow of working with hsxmpp is as follows,
  1. Authenticate to the server.
  2. Add handlers that will be called when a specified predicate is true. Every incoming message is an XML stanza. The library supplies predicates to decide if a message is a chat message, if it's a presence message, if it has body, etc. There are already some default handlers installed. For example, one handler is used to answer queries of the bot's software information.
  3. Your handler parses the incoming XML message using library functions. You can respond to the message by sending messages.
Refer to the Werewolf bot and my bot for more information.
 
hsxmpp contains an event loop: it waits for data from the network, treats it as events and handles them. The loop is in runXMPP in XMPPMonad.hs, and the actual waiting is in getString in TCPConnection.hs. The overall design is documented in the report. Others suggested using select(2), known in Haskell as forkIO. The design choice is fine, but it seems that it just walks through the list of installed handler and calls the first one whose preciate is met. I would like it to call other handlers on the list too. In addition, Avoid using waitForStanza because it seems to trap all messages and so no handler will be called.
 
Wishlist:
MUC apparently needs more work. Support for delHandler.
 
Todo:
migrate lambdabot.
 
Yipee! Just found roll your own bot.

‹ Prev    1-5 of 5    Next ›