It’s only slightly more tricky:
module type BaseT = sig
type base
val pr : base -> string
end
module type AbstT = sig
type abst
val pra : abst -> string
end
module BaseF (Abst : AbstT) = struct
type base = Ident of string | Abst of Abst.abst list
let pr = function
| Ident s -> s
| Abst al -> String.concat ", " (List.map Abst.pra al)
end
module AbstF (Base : BaseT) = struct
type abst = Foo of Base.base
let pra = function
| Foo b -> Printf.sprintf "Foo (%s)" (Base.pr b)
end
module rec Base : BaseT = BaseF(Abst)
and Abst : AbstT = AbstF(Base)
It’s also possible to simply make Base
and Abst
mutually recursive (without any functor), if you’re ok with having both implementations in the same file. And the middle-ground option, with one file defining a functor and the other one containing the recursive binding works too.
The main thing you should be careful about with recursive modules is the “safe module” constraint: every recursive cycle in a recursive module binding must contain at least one definition which only exports types (including module types if you want) and functions. (Or submodules with the same constraints. Classes are allowed too.)
Example:
module rec M : sig
type t
val compare : t -> t -> int
val default : t
end = struct
type t = T | S of S.t
let compare x y = 0
let default = S (S.singleton T)
end and S : Set.S with type elt = M.t = Set.Make(M)
The compiler will complain about the cycle M -> S -> M
, in which neither M
nor S
is safe (M.default
is not a function, and S.empty
isn’t either). In this particular case you can fix it by making default
a function (val default : unit -> t
and let default () = ...
), but sometimes it will not work.