How to neatly represent state changes and actions

I have a functionally pure core to a web server, which has an advance function:

advance : t -> event -> t * action list

This calls out to a bunch of utility functions:

foo : t -> t * action list

Is there a decent pattern for handling this? It feels kind of like a monad (in terms of tracking effects going through) however I don’t have sufficient knowledge of how to implement this.

You could use some variation of the state monad:

module Comp = struct
  type 'a t = action list -> ('a * action list)

  let (>>=) (x: 'a t) (f: 'a -> 'b t) : 'b t  = fun ls ->
    let (v, ls) = x ls in
    (f v) ls

  let get : action list t = (fun ls -> List.rev ls, ls)
  let put ls : unit t = (fun _ -> (), List.rev ls)
  let append action : unit t = (fun ls -> (), action :: ls)

  let return (x: 'a) : 'a t = (fun ls -> x, ls)

  let run (comp: 'a t) : 'a * action list =
    comp [] |> (fun (v, ls) -> (v, List.rev ls))

Then in your code:

  let example_command : int t =
    let (let+) x f = (>>=) x f in
    let+ () = append "hello" in
    let+ () = append "world!" in
    return 30

This might be overkill if you only ever want to append to the list of actions, in which case you could use a tuple monad instead:

  type 'a t = ('a * action list)

  let (>>=) (x: 'a t) (f: 'a -> 'b t) : 'b t  = 
    let (v, ls) = x in
    let (v, ls') = f v in
    (v, ls @ ls')

Brilliant thanks!! This is exactly what I was looking for!