Modular Explicits in OCaml › Modular Explicits [abs-modular-explicits]
OCaml 5.5 has module-dependent function types also known as modular explicits. The type of a function can now depend on types that come from a module that is also part of the function definition.
# let set_length : (module S : Set.S) -> S.t -> int =
fun (module S) v -> S.cardinal v;;
val set_length : (module S : Set.S) -> S.t -> int = <fun>
# set_length (module IntSet) IntSet.empty;;
- : int = 0
# set_length (module StringSet) StringSet.empty;;
- : int = 0
In fact, we can now return values of a particular type that depends on a particular module!
# let empty_set : (module S : Set.S) -> S.t =
fun (module S) -> S.empty;;
val empty_set : (module S : Set.S) -> S.t = <fun>
# empty_set (module IntSet);;
- : IntSet.t = <abstr>
# empty_set (module StringSet);;
- : StringSet.t = <abstr>
Or, we can dynamically select implementations and values.
# let with_impl :
config:string -> ((module S : Set.S) -> S.t -> 'a) -> 'a =
fun ~config fn ->
match String.lowercase_ascii config with
| "string" -> fn (module StringSet) StringSet.empty
| "int" -> fn (module IntSet) IntSet.empty
| c -> invalid_arg c
val with_impl : config:string -> ((module S : Set.S) -> S.t -> 'a) -> 'a =
<fun>
This is like a more usable version of the FCM-existential trick, though does require more manual type annotations on functions. For instance, I tried switching out OBuilder's use of existentials with modular explicits:
-type builder = Builder : (module Obuilder.BUILDER with type t = 'a) * 'a -> builder - let log tag msg = match tag with | `Heading -> Fmt.pr "%a@." Fmt.(styled (`Fg (`Hi `Blue)) string) msg | `Note -> Fmt.pr "%a@." Fmt.(styled (`Fg `Yellow) string) msg | `Output -> output_string stdout msg; flush stdout -let create_builder store_spec conf = +let create_builder : + Store_spec.store Lwt.t -> Native_sandbox.config -> + ((module B : Obuilder.BUILDER) -> B.t -> 'a) -> 'a Lwt.t = + fun store_spec conf fn -> store_spec >>= fun (Store_spec.Store ((module Store), store)) -> let module Builder = Obuilder.Builder (Store) (Native_sandbox) (Docker_extract) in Native_sandbox.create ~state_dir:(Store.state_dir store / "sandbox") conf >|= fun sandbox -> let builder = Builder.v ~store ~sandbox in - Builder ((module Builder), builder) + fn (module Builder) builder
I think the code is more pragmatic; there is no longer an indirection via a type called builder. The extra type annotations do add a little noise. Roll on modular implicits.