
On 20/08/05, Martin Vlk
Hi folks, I am new to Haskell, coming from an object-oriented realm.
As an exercise I am trying to develop an API for accessing/changing services configuration in a SysV-like init environment.
Being object-oriented I have defined a model of runlevels/services and a parser that creates instances of these structures from the underlying files. That way my program is able to interrogate the current state of the services configuration.
Now I would like to ask how to go about designing the part that will make changes to the config in respect to the following requirement.
I want the API to allow making changes only to the model and then applying them back to the filesystem at once. This is to support UI where the user will make various changes and then hit "Apply" or "Cancel". I am imagining this as a sort of simple transactional behaviour of the in-memory model.
At the moment I am thinking of passing the whole model around through the various API functions, that will make the changes, but there are many questions to be answered. I need to keep the original model state and a stack of the changes made so that at commit-time I can figure out what has been changed. Also some changes might affect each other (add service followed by remove service, etc.). Anyway the main question is how should such a scenario be approached.
Can anyone throw in some ideas to get me started? It might well be that this whole idea of a model with changing state is wrong in respect to FP. If so, what would the right way be?
Cheers vlcak
Well, it sounds like you want some kind of data type which represents the kinds of changes that can be made: data Change = AddService RunLevel ServiceID ... | RemoveService RunLevel ServiceID | ... You're then collecting up a bunch of changes to be made in some sort of data structure, the simplest case probably being a list, but it could be more specialised to the problem -- for instance, you might break the changes up based on their runlevel, depending on what is convenient. For example: type Changes = [Change] This is simple enough that it's questionable that it's worth defining an alias for it, but if you think you might change the way things are handled later, it can save a bit of typing. If you like, you can also do something like type Changes = Map RunLevel [Change] to keep changed separated by runlevel, depending on what you find convenient. If you determine that you need to keep track of more data, then this should become a proper datatype. You may want to make it a newtype anyway, just in the interest of discipline. If the user is going to make the changes through ticking checkboxes and such, you're probably going to be in the IO monad, so you could use an IORef which contains the current changeset, which you can then define functions to update in specific ways -- mostly adding new changes. IORefs are the Haskell take on mutable variables, and they're documented at http://www.haskell.org/ghc/docs/latest/html/libraries/base/Data.IORef.html You can also write pure(!) functions to, say, simplify a set of changes, removing extraneous updates, like the case which you mentioned where a service gets added and subsequently removed. These can be applied to the changeset in the IORef by way of the modifyIORef function. After the abstract side of things is satisfactory, and you can collect up changes with your user interface, you can write a suitable mapping which turns those changes into real world actions: enactChange :: Change -> IO () which converts an abstract change into an actual IO action to be performed, and of course: enactAllChanges :: Changes -> IO () which, if you chose the plain list option for storing the changes, is as simple as enactAllChanges = mapM_ enactChange or possibly something like enactAllChanges cs = mapM_ enactChange (simplifyChanges cs) Your actual design might be more complicated than this of course, but these are just some ideas as to how bits of it might look. Have fun, hope this helps :) - Cale