I am interested in learning about the best practices and recommended design patterns in 2024 for supporting concurrent IO in libraries. I noticed that several libraries release two packages, one using synchorious IO and another with a -lwt or -async suffix meant to be used in Lwt or Async workflows. An example is the [aws-s3 (GitHub - andersfugmann/aws-s3: Ocaml library to access Amazon S3) library. So:
Why is that?
Is this considered best practice?
How does one avoid code duplication if creating modules for sync and async modules?
Are there alternatives to this style?
Are there existing tutorials on this topic that I can learn from?
Any amount of insight is welcome since I have no idea where to start.
In short, lwt and async are two (incompatible) libraries exposing a monadic API for concurrency. Together, they are the de-facto standard for concurrent programming (at least before the arrival of OCaml 5, see below).
With OCaml 5 it became possible to implement concurrency in “direct style” (ie without using monads or callbacks). Accordingly, there are now concurrency libraries which follow this style:
Thanks a lot for this reply, I wasn’t aware of the existence of miou. Are there any recommended best practices to follow when implementing new libraries, given all the options stated above? Is there any benefit with trying to support multiple concurrency libraries or is it better to just stick with one implementation and forget the rest?
I would say that any recommendation would depend on what you are trying to achieve.
In the past, people have abstracted over the Lwt/Async/eio/etc difference by functorizing their libraries, but this complicates the implementation, and can get tricky if you need to go beyond the “formal” properties of each concurrency framework (since you need to come up with a signature common to all concurrency frameworks). An example of this approach is GitHub - mirage/ocaml-cohttp: An OCaml library for HTTP clients and servers using Lwt or Async.
The maximally flexible approach is to write your library in so that it does not do any I/O by itself, and instead asks the library user to do it on its behalf. Then, by construction, the library will be independent of any specific concurrency framework. The downside is that this often requires more work to design & implement and to develop the connectors to specific concurrency networks. An example of this approach is GitHub - mirleft/ocaml-tls: TLS in pure OCaml.