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.

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))
end

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')