On 20/08/05, Martin Vlk <vlca01 at tiscali.cz> wrote:
> 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