UI Time Travel Debugging in Ocaml


Hello Everyone,

I am still working on my web application and I want to try and implement time travel debugging on the front end similar to this elm project here https://github.com/rtfeldman/elm-spa-example (the demo with time travel debugging, directly here http://rtfeldman.github.io/elm-spa-example-with-debug/). The ultimate end goal will be to have the program record all of the actions that the ui fires and give the user the option to look at them or even play them back (hence the time travel). a stretch goal if I can implement all that would be to allow for the user to export and import these logs and have the application run through them as if the user were hitting the buttons or performing the actions themselves.

Ive been looking through the Incr_dom documentation (which is what Im using on the front end) and I noticed that Action has a built in val should_log: t -> bool. I thought to myself im golden becuase it has to be what im looking for but now after fiddling with it for a while im not totally sure its the silver bullet i was looking for. Ive looked through much of the rest of the incr_dom documentation and some of the vdom docs in case and havent found anything that looks like it is going to perform the actions I need.

I was wondering if anyone had done this before or had resources they could point me to to help me progress, I have been reading a lot of blog posts and repo notes about doing this sort of thing in Elm in the hopes of finding some references to ocaml (and while I did find some none really panned out as they all tended to refer to ocamldebug and its time travel features which dont help me … I think …) at this point im not even sure what to google because “time travel debugging ocaml” just leads me to the ocamldebug which I feel is not the exact resource im looking for. there are plenty of elm resources for this so that is reassuring it definitely makes me think that this is possible in ocaml!

Thank you all for your help.


I’m glad you’re thinking about this, and I do think that this would be fairly natural to do within Incr_dom. I don’t think you the should_log call is really relevant here, but the basic structure of an Incr_dom app fits the time-travel idea quite well. (and yes, ocamldebug will not help you here.)

I think you can add time-travel to an app in a quite generic way without modifying the incr_dom itself. My thought would be to write a functor that can take something that matches, say, Incr_dom.App_intf.S_simple, and produces a new module that matches Incr_dom.App_intf.S_simple, but that does the time-travel work.

The key thing you need to do for time-travel is to capture and remember the initial state, as well as the full sequence of Action.t’s. It then needs to add some kind of overlay to the UI that allows you to do the actual time-travel work, e.g., by letting you change the model to be what it was at some point in the execution of the system, by replaying the actions from the beginning. (You could also keep occasional snapshots if you want to speed up replay.)

One limitation is that this won’t do anything to recover the imperative part of the state, so this makes the most sense for apps that have very limited imperative state. Hard to see how one would really do much better, since things like open websocket connections are not going to react well to time-travel…



so I have spent the last couple days mulling over your suggestions and trying some things out and i have a couple more questions.

why would i want to make a functor on the incr_dom.app_intf.s_simple, i thought it would be enough to just track the actions and the initial model that way i can just execute any actions that happen between the beginning and the current time on the initial version of the model and it will show the changes up to that point? Would it give me some advantage to work on the whole app_intf level instead of just the proverbial “actions applied to the model” level?

Where do i want this functor to live, I’ve tried putting it in the model but that doesnt seem to work for a whole slew of reasons most of which boil down to i am unsure about how to make a seperate module part of the type of the model, i have played with it living as part of the action module but that makes it hard to get out of the module to operate on it in the model to display it later on, and I thought about making it global and passing it around from there which seemed like the most practicable plan but its 1) not terribly (or at all) functional and 2) i dont know how to run the functor if its just hanging out. i figured i could just always update the action_list in the apply_action function but then i would need to pass the action list or the functor into the apply_action which I cannot since apply_action is from incr_dom and has a signature it needs to adhere to

also i was thinking that im going to run into an issue with calls to the database on the back end wont i, since any changes that occur on the back end have nothing to do with changes to the model, i.e. the model reflects the database not the other way around.



Functors are a really powerful feature of OCaml. In this particular scenario you can use them to extend existing app_intf module with new functionalities. In simple terms functors are functions that work on a module and return a new module. So you could imagine a functor that works on a App_intf.s_simple and returns an enhanced module that contains your implementation of the work needed to support the time travel debugging. Note that you could potentially implement the same as part of your specific application, but then that implementation is tied down to your particular application and isn’t generic to work for any other incr_dom app.

I’d definitely recommend reading: https://dev.realworldocaml.org/functors.html to learn more about what’s possible to achieve using Functors.

As for how to use it, you would use it with the Start_app just like a regular module that satisfies the App_intf interface. What changes is that the functor enhances the module before you pass it along to the Start_app.

Like Yaron said, recovering imperative state wouldn’t work here. In my opinion this is where its important to spend some time to think about the scenarios where this style of debugging should be used. Personally for me, it adds value in situations where you care about getting a sequence of steps that can be used to find out how you reached a particular place in the application. But I don’t see it being useful to debug issues in components that are outside the scope of the UI application (HTTP Calls, websocket connections, database state etc).