Like many of you, I’ve been looking into migrating my libraries from Lwt to Eio to take advantage of OCaml 5’s direct-style concurrency. While the manual migration guides are excellent, I found myself making the same mechanical changes over and over—flattening binds, unwrapping promises, and rewriting maps.
So, I built lwt-to-eio, a tool that automates the boring parts of this transition.
It parses your OCaml source code and performs AST-level rewrites to transform monadic Lwt patterns into direct-style Eio code.
Input (Lwt):
let fetch_data id =
Lwt.bind (Db.get_user id) (fun user ->
Db.get_posts user.id >>= (fun posts ->
Lwt_list.map_p process posts
)
)
Output (Eio)
let fetch_data id =
let user = Lwt_eio.Promise.await_lwt (Db.get_user id) in
let posts = Lwt_eio.Promise.await_lwt (Db.get_posts user.id) in
Eio.Fiber.List.map process posts
Current Status & Help Wanted
This is currently an MVP. It handles the most common patterns I encountered (binds, map_p, sleep, return), but there is plenty of surface area left to cover.
I’m looking for contributors to help add rules for other common patterns. I’ve tagged a few issues as “Good First Issues” if anyone wants to dip their toes into ppxlib and AST rewriting:
Lwt.catch → try/with: Transforming monadic error handling to direct style exceptions.
Lwt_io support: Mapping legacy IO functions to Eio buffers.
Feedback, PRs, and “It broke on my file!” reports are all very welcome.
Lwt 6.0 has just been released (announce thread) with support for direct-style usage via a Lwt_direct – see lwt_direct.mli. Have you considered having a version of your tool to convert from standard Lwt style to Lwt_direct?
I suppose the output on this example could be as follows:
let fetch_data id =
let user = Lwt_direct.await (Db.get_user id) in
let posts = Lwt_direct.await (Db.get_posts user.id) in
Lwt_list.map_p process posts
Thanks for the heads up, @gasche! I will look closely at the Lwt 6.0, the direct-style syntax seems much cleaner. A transformation to Lwt_direct seems like a great addition to the tool.
I’ve taken a closer look on ‘ciao-lwt’. It’s a powerful tool, but fairly heavy due to reliance on ‘ocaml-index’.
I want to keep ‘lwt-to-eio’ as a lightweight alternative that can run without a full build environment. That said, I will definitely study their apporach to Lwt.catch for inspiration!