I just released the initial version of the changeset library, and its companions changeset-containers: Stateful monad transformer based on monoidal actions and changeset-lens: Stateful monad transformer based on monoidal actions.
It contains a very general state monad that allows you to restrict, inspect, and edit the changes you are about to perform.
Motivation
Imagine you have a big data structure s, and you only want to perform a few specific kinds of updates. s might be a huge record, and you want to allow only a particular kind of benign changes to one field of that record.
Then you’d have:
data User = User
{ name :: Text
, password :: Hash
, ... -- Oodles of other fields
, addresses :: [Address]
, ...
}
We want to change User, but only by adding more addresses to the list. Then we could define a type
data AddAddress = AddAddress Address
which denotes the change of adding an address to the user.
Its semantics are implemented with a type class:
instance RightAction AddAddress User where
actRight user (AddAddress a) = user { addresses = a : addresses user }
The library changeset defines a new monad transformer where you can, as a first order effect:
- read the current state of type
User - add new changes of type
AddAddress
You can also, as higher order operations review and revise all the currently to be applied. This is very useful if you want to have a later part of the program roll back, or log certain changes.
For example in a big web application with a huge Model, you would typically pass around changes to tiny parts of the model. Often the situation arises where we have a potentially updated model, but depending on the context we only want to allow some changes, and not others.
Theory
The gist is this:
- The new monad
ChangesetT s w m ais isomorphic tos -> m (w, a) wis a monoid of reified changes tos.- Define a right action of
wons:actRight :: s -> w -> swhich contains the semantics of the change. - Running a
ChangesetT s w m arequires an initial states, and returns am (a, s), that is, an effect in the background monadm, a value, and the changed states.
Compare to usual state: State s a is isomorphic to s -> (s, a). You cannot inspect the change itself, only what it does to the whole, possibly intractable value.
Comparison
changeset compares to a few different concepts around there:
AccumT: The inspiration forChangesetTwas to generaliseAccumTto situations where the current state is not aMonoid, but the changes to it are. SoAccumTis a special case ofChangesetT.StateT: Is a special case ofChangesetTas well- Conflict-free replicated data types: You could model these in
ChangesetT. - Database transactions: When
sis the state of a database andwis a query, thenChangesetT s w m ahas the semantics of a transaction.
Ecosystem
- changeset-containers: Changesets for standard
containers - changeset-lens: Inspectable changes for lensy data structures
- changeset-reflex: WIP implementation of utilities to pass around changes in the reflex FRP framework
Planned: Integration in fused-effects, rhine, …
Questions to you, if you’re willing to play around with changeset
- Any general feedback on the design of the library
- Is this useful to you?
- Is it ergonomic enough? What are the pain points?
- Do you have an application in mind, but are unsure how to make it work?
- Is there any other library you would want to have this integrated in? (E.g. effect framework, FRP framework)