The following is a minimal example of the shape of what’s proven to be a very stubborn problem for me. Thanks in advance to everyone that takes the time to read this and maybe help get me unstuck. A link to the full code shown here is at the end.
Presume you have some number of polymorphic variant types, with natural subtyping relationships, e.g.
type a = [ `A of int ]
type b = [ a | `B of int ]
Those relations flow through nicely to things like List.t
and other types that are defined with type parameters either explicitly marked as covariant or inferred as such, e.g.
let pair (a : a) (b : b) = ([a] :> b list) @ [b] (* works fine *)
However, I have some data structures that need some helper functions suitable to the types they are to contain. The most natural and typesafe way to set this up seemed to be to define each data structure as a functor, and have the contained type and helper functions provided by an argument:
module type Config = sig type t end
module type Box = sig
module C : Config
type t = { regions : C.t list }
val empty : t
val add : C.t -> t -> t
val get : t -> C.t list
end
module Box (C: Config) : Box with type C.t = C.t = struct
module C = C
type t = { regions : C.t list }
let empty = { regions = [] }
let add r box = { regions = r :: box.regions }
let get box = box.regions
end
module ABox = Box(struct type t = a end)
module BBox = Box(struct type t = b end)
(Of course the actual functor argument type is much more than just the member type; just presume the real-world analogue of Box
uses a bunch of functions provided by Config
.)
This approach has been working just fine for me for months. However, I now need to be able to treat the more constrained instances of these data structures as if they were more general (just as pair
above did in coercing a list :> b list
). Alas:
(* Error: Type ABox.t is not a subtype of BBox.t *)
let join (abox : ABox.t) = BBox.get (abox :> BBox.t)
FWIW, feedback I’ve gotten in irc mostly gives me the impression that I’m fairly misguided either in my use of modules and functors or in my expectations w.r.t. OCaml’s type system (specifically, the degree to which polymorphic variant subtyping relations can or should affect subtyping relations of higher-order types). Accomplishing this sort of thing is generally pretty straightforward in other languages (granted, perhaps due to various unsoundness there); any suggestions as to how to accomplish what I’ve described here (or maybe confirmation that I am actually misguided) would be greatly appreciated.