First class module types can encode the relations you want, but getting back to a regular module type is a bit hackish:
module type A = sig
type a
type t
end
type 'a m = (module A with type a = 'a and type t = 'a option)
module type A1 = module type of (val ((Obj.magic ()) : int m))
There is no safe use of Obj.magic, so I think your solution with functors is better (unless you only need first-class modules anyway, in which case you don’t need the definition of A1 and its magic).
module type A =
sig
type t
type a
val f1 : t -> a
val f2 : a -> t
end
module rec A_aux : A with
type t = int and
type a = A_aux.t option
= A_aux
module type A1 = (module type of A_aux)
I’m not sure how fast it breaks down though (meaning if your types are slightly more complex or you have
other definitions in A or if you want a and t to be mutually recursive).