[Haskell-cafe] Writing an IRC bot, problems with plugins

Don Stewart dons at galois.com
Wed Nov 5 15:26:23 EST 2008

>    My general idea is to have the main application listening to the network
>    socket and then calling all the plugins on each incoming message.
>    Therefore I maintain a list of plugin states in the main application's
>    state and on each incoming message I call a function which modifies the
>    plugin's state.

Like lambdabot!
>    There's a PluginClass class which contains definitions of functions for
>    each plugin which they all share. Simplyfied it's like this:
>    type PL = StateT PluginConfig
>    class PluginClass a where
>        identifier :: a -> String
>        rawMessage :: (MonadIO m) => a -> Message -> PL m ()

Like lambdabot, kind of.
>    So plugins can be identified uniquely using the identifier function and
>    they can respond to messages using the rawMessage function. This function
>    is executed in the PL monad, which is essentially a StateT monad for
>    updating the plugin's state trough put and maybe accessing a few data
>    fields from the underlying Bot monad in which the main application is
>    operating.
>    Then again I want to be able to query a plugin's state from a different
>    plugin. For instance I'll have a plugin which keeps track of the channels
>    the bot has joined collecting user information, the topic, etc. Another
>    plugin could then query the "chan info" plugin and get all the users in a
>    certain channel through a queryPlugin function which takes a plugin and
>    looks that plugin up in the main application's plugin state list for the
>    right state and then calls a function on it. The plugin and the
>    corresponding functions would be exported by the plugin's module.
>    queryPlugin :: (PluginClass a) => a -> (a -> b) -> PL m b
>    queryPlugin pl f = do
>         plugins <- getGlobalPlugins -- ideally (PluginClass a) => [a]
>        let pluginNames = map identifier plugins
>            targetName = identifier pl
>            [(_, target)] = filter ((==) targetName . fst) (zip pluginNames
>    plugin)
>        return (f target)
>    But here I am facing either one or the other problem, depending on the
>    "solution."
>    1) I somehow have to store all the plugin states in the main application.
>    Since plugins are essentially their states, they are quite arbitrary. I
>    either cannot use a list for that or I have to use existential types which
>    would make that possible.
Existential types are used in lambdabot for this. I'd probably use an
associated type to connect the plugin to its state type now, too.

>    2) Using an existential plugin type would restrict the functions I am able
>    to call on the plugin to those which are supported by the PluginClass.
>    This would render queryPlugin unusable since the functions a plugin
>    exports for query the state are arbitrary.

You might be able to design around this. Lambdabot manages ok with
existentially typed interfaces.

  -- Don

More information about the Haskell-Cafe mailing list