I have an underlying, low-level module with the following signature (simplified from my original use-case but equivalent):
module type CACHE = sig … end module type FOOBAR = sig … end module Instantiate (F: FOOBAR) (H: Hastbl.HashedType) : CACHE
FOOBAR stuff is nitty-gritty details that the user should not see. So I have the following over-layer (also simplified but still equivalent):
type color = Red | Blue type shade = Light | Dark let foobar_of_parameter color shade = match color with | Blue -> (module Blue : FOOBAR) | Red -> match shade with | Light -> (module Pink : FOOBAR) | Dark -> (module Red : FOOBAR) module type MAKE = functor (H: Hashtbl.HashedType) -> CACHE type make = (module MAKE) let make (c : color) (s : shade) : make = let module Foobar = (val foobar_of_parameter c s: FOOBAR) in let module M = Instantiate (Foobar) in (module M)
That works. Specifically, it (1) compiles without complains and runs fine, and (2) it presents the end-user with two independent parameters that fit better with the narrative of the library than the
FOOBAR parameters of the low-level
However, it’s a bit annoying to have to mix first-level module and functors. So I wanted to make a fully-functorised version. There is a bit of boilerplate and then the core functor looks like this (again, simplified but equivalent):
module type PARAM = sig val c: color val s: shade end module Make (P: PARAM) (H: Hashtbl.HashedType) = Instantiate (val foobar_of_parameter P.c P.s: FOOBAR) (H)
However, this functor application fails with the error:
This expression creates fresh types. It is not allowed inside applicative functors.
Is it possible to have a fully-functorised version?
I’m also open to another approach that would simply expose the more intuitive parameters.
My real use case is at https://gitlab.com/nomadic-labs/ringo/-/blob/more-uniform-variants/src/ringo.ml and it’s a little bit more complicated: there is one more parameter and the instantiation functor takes one more module. Also note that some of the parameter (
retention in the real case) will eventually grow to include more variants. Thus, it is preferable to avoid solutions that grow with the size of the parameter space.