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

Alexander Foremny alexanderforemny at googlemail.com
Wed Nov 5 10:34:23 EST 2008


Hello, Community.

I already bothered #haskell a few times with the very same problem and
always got interesting responses. But I seem to have always simplified my
problem too much ending up with an helpful answer to my (simplified)
problem, but not with a real solution. Thus I'm giving the mailing list a
try where I don't feel like having to wrap up my concerns in as few lines as
possible.

I am writing an single server, multi channel IRC bot with the support of
plugins and limited plugin communication. With the plugin system I am facing
problems I cannot really solve myself.

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.

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 ()

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.

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.

3) I could use Dynamics to store the plugin in a list *and* call arbitrary
functions, but then again how would I run a plugin? All the main application
know about the plugin state is that all the functions defined by PluginClass
are callable on the state. But the type (PluginClass a) => a isn't enough to
unwrap the Dynamic, apply the function and wrap it again.

Another suggestion was to not use a class but make each plugin a record,
exporting the functions itself. Though I haven't given that a serious
thought, it seems not ideal to me. Using a class a plugin can define the
functions it actually uses. In my real code, they're about 12 functions the
class exports and there could be more. Using a record I would have to
implement all possible functions even though they're not changing the
plugin's state nor causing side effects.

The most obvious solution would be to use an algebraic data type with a
constructor for each plugin. But I'd like to develop the plugins independent
from the core and I'd like to make them more dynamic than that, maybe
implementing dynamic loading of plugins at some time.

Then there are two other potential solutions, but I haven't looked into them
seriously since they seemed a little hackish to me at first glance.
One would be using Hlist (which seems to be over-sized for my problem as
well) and another would be to store a tupel of (Dynamic, (PluginClass a) =>
(Dynamic -> a)) as plugin state list where the first element would be the
plugin's state wrapped in a dynamic and the second a unwrap function
exported by the plugin's module. I *might* get around the problem of being
to unspecific about the type when unwrapping, but this idea came only at the
point of writing this email and I would expect running into the same
problems I have: It can be achieved by either being too general or being to
specific.

Hopefully I've explained everything well enough while not being too long
with all this.

Thanks in advance for your help!
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.haskell.org/pipermail/haskell-cafe/attachments/20081105/80a66112/attachment.htm


More information about the Haskell-Cafe mailing list