# Changeset package released

**URL:** <https://discourse.haskell.org/t/changeset-package-released/11380>\
**Category:** Announcements\
**Created:** [February 10, 2025, 8:14pm UTC](https://discourse.haskell.org/t/changeset-package-released/11380 "2025-02-10T20:14:36Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![turion](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/turion/32/989_2.png) [@turion](https://discourse.haskell.org/u/turion)\
**Post date:** [February 10, 2025, 8:14pm UTC](https://discourse.haskell.org/t/changeset-package-released/11380/1 "2025-02-10T20:14:36Z")

</div>

I just released the initial version of the [`changeset`](https://hackage.haskell.org/package/changeset) library, and its companions [changeset-containers: Stateful monad transformer based on monoidal actions](https://hackage.haskell.org/package/changeset-containers) and [changeset-lens: Stateful monad transformer based on monoidal actions](https://hackage.haskell.org/package/changeset-lens).

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:

```haskell
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

```haskell
data AddAddress = AddAddress Address

```

which denotes the change of adding an address to the user.  
Its semantics are implemented with a type class:

```haskell
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:

1. read the current state of type `User`
2. 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:

1. The new monad `ChangesetT s w m a` is isomorphic to `s -> m (w, a)`
2. `w` is a monoid of reified _changes_ to `s`.
3. Define a _right action_ of `w` on `s`: `actRight :: s -> w -> s` which contains the semantics of the change.
4. Running a `ChangesetT s w m a` requires an initial state `s`, and returns a `m (a, s)`, that is, an effect in the background monad `m`, a value, and the changed state `s`.

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`](https://hackage.haskell.org/package/transformers/docs/Control-Monad-Trans-Accum.html#t:AccumT): The inspiration for `ChangesetT` was to generalise `AccumT` to situations where the current state is not a `Monoid`, but the changes to it are. So `AccumT` is a special case of `ChangesetT`.
- `StateT`: Is a special case of `ChangesetT` as well
- [Conflict-free replicated data types](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type): You could model these in `ChangesetT`.
- Database transactions: When `s` is the state of a database and `w` is a query, then `ChangesetT s w m a` has the semantics of a transaction.

## Ecosystem

- [changeset-containers](https://hackage.haskell.org/package/changeset-containers): Changesets for standard `container`s
- [changeset-lens](https://hackage.haskell.org/package/changeset-lens): Inspectable changes for lensy data structures
- [changeset-reflex](https://hackage.haskell.org/package/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)

---

<div class="post-metadata">

**Author:** ![jackdk](https://avatars.discourse-cdn.com/v4/letter/j/9f8e36/32.png) [@jackdk](https://discourse.haskell.org/u/jackdk)\
**Post date:** [February 10, 2025, 8:58pm UTC](https://discourse.haskell.org/t/changeset-package-released/11380/2 "2025-02-10T20:58:42Z")

</div>

Cool idea, I haven’t seen anyone try to mix actions and transformers before. Building your updates out of actions and making your action type a Group seems like a promising way to set up undo/redo in a larger program.

I’m curious what made you favour right actions over left actions, as I find the type of `actLeft :: w -> s -> s` much more intuitive (as with `containers` and `lens`, it makes partial application return a function over the larger structure).

Also, how’s the type inference? Most typeclasses representing actions have two unconstrained type variables which can make inference difficult.

---

<div class="post-metadata">

**Author:** ![turion](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/turion/32/989_2.png) [@turion](https://discourse.haskell.org/u/turion)\
**Post date:** [February 11, 2025, 3:40pm UTC](https://discourse.haskell.org/t/changeset-package-released/11380/3 "2025-02-11T15:40:08Z")

</div>

> [@jackdk](#):
>
> I’m curious what made you favour right actions over left actions, as I find the type of `actLeft :: w -> s -> s` much more intuitive

That’s a great point. The initial WIP version used `Action` (which is a left action) from Brent Yorgey’s excellent `monoid-extras` package. I would have loved just piggybacking on this, and put in quite some effort to make it work. But it turned out to be unintuitive:

For a left action you expect a generalized associativity axiom to hold:

```haskell
m1 <> m2 `act` s == m1 `act` m2 `act` s

```

So `m2` acts on `s` first, and `m1` second. But imagine you’d add two changes in `changeset`:

```haskell
do
  change m1
  change m2

```

I think that intuitively you’d expect that `m1` is applied first, and `m2` second. `WriterT` and `AccumT` behave that way.

But you would also expect that `change` is a monoid morphism:

```haskell
change m1 >> change m2 == change (m1 <> m2)

```

These two observations force you to adopt a right action instead of a left action, I believe. And yes, this makes a few situations a bit awkward. But for some consolation, consider how adding several changes feels similar to the `&` operator:

```haskell
s & execChangeset (change m1 >> change m2)
==
s & execChangeset (change m1) & execChangeset (change m2)

```

> [@jackdk](#):
>
> Also, how’s the type inference? Most typeclasses representing actions have two unconstrained type variables which can make inference difficult.

You’re absolutely right to be concerned. In general you will have to annotate state or change types in many places. I considered introducing a functional dependency, but I still believe it’s too restrictive.

---

<div class="post-metadata">

**Author:** ![olf](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/olf/32/3813_2.png) [@olf](https://discourse.haskell.org/u/olf)\
**Post date:** [February 13, 2025, 9:33pm UTC](https://discourse.haskell.org/t/changeset-package-released/11380/4 "2025-02-13T21:33:19Z")

</div>

This reminds me of [delta encodings](https://bobkonf.de/2023/apfelmus.html) as advertised by Heinrich Apfelmus.

---

<div class="post-metadata">

**Author:** ![olf](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/olf/32/3813_2.png) [@olf](https://discourse.haskell.org/u/olf)\
**Post date:** [February 15, 2025, 10:00am UTC](https://discourse.haskell.org/t/changeset-package-released/11380/5 "2025-02-15T10:00:43Z")

</div>

> [@turion](#):
>
> You cannot inspect the change itself, only what it does to the whole, possibly intractable value.

If that is the problem that changeset solves, then from any `MonadChangeset` I would expect to be able to get hold of the accumulated change `w`, not the state, like the `listen` action of `MonadWriter`. Otherwise, why would I accumulate changes to apply later with `change` when I can not inspect and transform the changes?

```haskell
\f -> ChangesetT (\s -> fmap ((\(w,a) -> (w,(w,a)))) (getChangesetT f s))

```

Can one define this for any `MonadChangeset`?

Also, could one not define

```haskell
MonadChangeset s w m = (MonadState s m, MonadWriter w m, RightAction w s) 

```

and obtain essentially the same capabilities?

```haskell
import Control.Arrow ((&&&))
import Control.Monad.State.Class
import Control.Monad.Writer.Class
current = get
change w = modify (flip actRight w) -- and tell w?
changeset f = state (f &&& id) >>= (\(a,w) -> tell w >> return a)

```

---

<div class="post-metadata">

**Author:** ![turion](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/turion/32/989_2.png) [@turion](https://discourse.haskell.org/u/turion)\
**Post date:** [October 1, 2025, 9:14am UTC](https://discourse.haskell.org/t/changeset-package-released/11380/6 "2025-10-01T09:14:00Z")

</div>

> [@olf](#):
>
> If that is the problem that changeset solves, then from any `MonadChangeset` I would expect to be able to get hold of the accumulated change `w`, not the state, like the `listen` action of `MonadWriter`

Yes, you can do that. See this section: [Control.Monad.Trans.Changeset](https://hackage-content.haskell.org/package/changeset-0.1.0.3/docs/Control-Monad-Trans-Changeset.html#g:4)

For example, there is this function:

```haskell
changelog :: forall (m :: Type -> Type) s w a. Functor m => ChangesetT s w m a -> ChangesetT s w m (a, w) 

```

This is the analogon to `listen`.

> [@olf](#):
>
> Also, could one not define
> 
> ```haskell
> MonadChangeset s w m = (MonadState s m, MonadWriter w m, RightAction w s) 
> 
> ```
> 
> and obtain essentially the same capabilities?

As an external API, yes. But you can’t straightforwardly replace `ChangesetT` by a stack of `StateT` and `WriterT`, because that wouldn’t bind correctly. As you’ve noticed in your implementation of `change`, we need to update both the state and the change. So `ChangesetT` has a special monad instance that is not covered by the existing transformers. (Yes, I could newtype a small stack of state and writer, and give it that special monad instance, but I don’t see a point when I could just write a new transformer and have a much clearer implementation.)

---

<div class="post-metadata">

**Author:** ![turion](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/turion/32/989_2.png) [@turion](https://discourse.haskell.org/u/turion)\
**Post date:** [October 1, 2025, 9:26am UTC](https://discourse.haskell.org/t/changeset-package-released/11380/7 "2025-10-01T09:26:06Z")

</div>

> [@olf](#):
>
> If that is the problem that changeset solves, then from any `MonadChangeset` I would expect to be able to get hold of the accumulated change `w`, not the state, like the `listen` action of `MonadWriter`

Ah, apologies, I see your point! I was focussing on `ChangesetT` and not `MonadChangeset`. The API of `MonadChangeset` is in fact incomplete. I could add a method like `MonadChangeset s w m => m a -> m (w, a)`, but then I can’t figure out how to write a generic implementation for any transformer.

Maybe there should be a second type class `MonadChangelog` that adds this method?
