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 a
is isomorphic tos -> m (w, a)
w
is a monoid of reified changes tos
.- Define a right action of
w
ons
:actRight :: s -> w -> s
which contains the semantics of the change. - Running a
ChangesetT s w m a
requires 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 forChangesetT
was to generaliseAccumT
to situations where the current state is not aMonoid
, but the changes to it are. SoAccumT
is a special case ofChangesetT
.StateT
: Is a special case ofChangesetT
as well- Conflict-free replicated data types: You could model these in
ChangesetT
. - Database transactions: When
s
is the state of a database andw
is a query, thenChangesetT s w m a
has the semantics of a transaction.
Ecosystem
- changeset-containers: Changesets for standard
container
s - 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)