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!