Maintaining two versions of a module

Say I have a module A that is used throughout my code, and I have two possible implementations of this module, say A1 and A2. (Maybe A1 is more obviously correct, while A2 is more efficient.) By declaring A1 and A2 to implement the same module type and depending only on that signature in the rest of my code, I can guarantee that my code will compile with either implementation.

However, I would also like to regularly compile and test my code with both implementations. I can do this in a kludgy way by copying a1.ml or a2.ml onto a.ml every time I want to switch implementations, but is there a better way? Perhaps involving making them both into libraries or something? The ideal would be to just be able to tell dune which implementation I want to build with each time (or when I want to switch implementations) without having to modify the code.

Maybe this feature could help? (never tried it though)

Wow, that looks like exactly the sort of thing I want! I’ll give it a try.

The utop limitation is sad though; it sounds like a project using virtual libraries basically cannot be loaded in the toplevel? It would be a lot nicer to be able to choose an implementation when loading a toplevel.

Another way is to make the code that uses A1 and A2 a functor over the common signature. Then you can compile and test both implementations at the same time, purely within the language, without the need of any extra support from the build system.

Cheers,
Nicolas

1 Like

Yes, a functor is the first thing I thought of. But then I have to change the code every time I switch implementations, to specify whether the functor is applied to A1 or to A2.

Also a functor, like a module, can’t be split over multiple files, so there would be a lot of bookkeeping in defining a separate functor inside each file of my project and applying them all to the correct modules whenever they’re used.

You can use first class modules to pick at runtime:


module type T = sig val print : unit  -> unit end

module A = struct let print () = print_endline "A" end

module B = struct let print () = print_endline "B" end

module C = (val if Random.int 2 = 0 then (module A : T) else (module B : T))

(*
module M = F C
etc 
*)
2 Likes

A similar sort of solution in Dune might be to use Alternative Dependencies along with a script that makes different dependencies available depending on which backend you’re testing. I have not done any of this and I find Dune to be very difficult to use, but I was reading about this and thought it might apply to your situation.

Thanks for mentioning alternative dependencies, but I don’t see how to use it right away since it seems to condition only on what is installed rather than on some flag that I can easily flip manually.

The first-class modules trick is cute. I guess instead of being random, the selection could be done based on a command-line argument to the executable. What about when loading the code in utop – is there some way to pass a flag to utop that could be detected by the code that selects a first-class module?