问题
I'm writing a modular and extensible text editor in Haskell and I'd like to implement plugins in such a way: The writer of the plugin provides a single function that looks something like this:
handleEvent :: (PluginState, EditorState) -> Event -> (PluginState, EditorState)
As each event occurs the plugin can use the current editor state and customized chunk of its own state to calculate a new editor state and new plugin state. Of course each Plugin is going to have a different Type for the Plugin state, so I'm getting stuck on how I can integrate this into my system in a general way.
How can I write something vaguely like this:
type Plugin = (PluginState, EditorState) -> Event -> (PluginState, EditorState)
data MyEditor = MyEditor EditorState [Plugin] [PluginState]
When PluginState isn't a concrete type?
TLDR; How can I store a map of values with non-concrete types in an accessible way without baking in every plugin's state-type into my global state? I'm okay with re-compiling the editor when a new plugin is added.
Thanks! I'm really stuck on this one :/
If you need any clarification please just ask!
回答1:
Of course each Plugin is going to have a different Type for the Plugin state, so I'm getting stuck on how I can integrate this into my system in a general way.
Perhaps you could use an existential type to hide the plugin state, something like
{-# LANGUAGE ExistentialQuantification #-}
data Plugin = forall ps. Plugin {
currentState :: ps
, transition :: ps -> EditorState -> Event -> (ps, EditorState)
}
handleEvent :: Plugin -> EditorState -> Event -> (Plugin,EditorState)
handleEvent (Plugin ps t) es e =
let (ps',es') = t ps es e
in (Plugin ps' t,es')
Now each plugin is of the same type, and yet different plugin values can have internal states of different types:
charPlugin :: Plugin
charPlugin = Plugin 'a' (\ps es e -> (succ ps,es))
intPlugin :: Plugin
intPlugin = Plugin (1::Int) (\ps es e -> (succ ps,es))
(I took inspiration from the Fold type from the foldl package, which uses existentials in a similar way.)
You can now have a list of plugins:
plugins :: [Plugin]
plugins = [charPlugin,intPlugin]
A possible evolution of the design would be to constrain the internal states to be instances of some typeclass:
data Plugin = forall ps. Show ps => Plugin {
currentState :: ps
, transition :: ps -> EditorState -> Event -> (ps, EditorState)
}
I suspect a Monoid
instance could be defined for the Plugin
type.
Also, we could think of explicitly parameterizing Plugin
on the type of events it accepts, like
data Plugin e = ...
In that case Plugin could be made an instance of Contravariant and perhaps Divisible as well.
And if we go wild and parameterize on the editor state
data Plugin es e = ...
then perhaps we could find a way to "zoom" a given plugin to work in a more general state than the one for which it was defined.
来源:https://stackoverflow.com/questions/40698396/how-can-i-handle-user-plugins-in-my-types