Not sure if I should make a Github issue about this, but today I ended up burning a decent amount of time as a result of a confusing error message. Here’s a simplified example[1], which uses multiple with type constraints to constrain a first-class module parameter:
module type Graph = sig
type t
type vertex
val empty : t
val vertex : vertex -> t
val overlay : t -> t -> t
val connect : t -> t -> t
end
module Data (V : sig type t end) = struct
type t =
| Empty
| Vertex of V.t
| Overlay of t * t
| Connect of t * t
type vertex = V.t
let fold (type g) (module G : Graph with type t = g with type vertex = vertex) : t -> g =
let rec fold = function
| Empty -> G.empty
| Vertex x -> G.vertex x
| Overlay (x, y) -> G.overlay (fold x) (fold y)
| Connect (x, y) -> G.connect (fold x) (fold y)
in
fold
end
File "scraps/misc_algebraic_graphs.ml", line 23, characters 32-79:
23 | let fold (type g) (module G : Graph with type t = g with type vertex = vertex) : t -> g =
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: Syntax error: invalid package type: only module type identifier and "with type" constraints are supported
I thought the error meant either that:
- multiple
with typeconstraints were not supported with first-class modules, or - that constraining it by a concrete type (i.e.
vertex) ws not supported
I tried working around this limitation by making a new module type that constrained the vertex type separately, but it was not an ideal solution:
module Data (V : sig type t end) = struct
type t =
| Empty
| Vertex of V.t
| Overlay of t * t
| Connect of t * t
type vertex = V.t
module type Graph' = Graph with type vertex = vertex
let fold (type g) (module G : Graph' with type t = g) : t -> g =
let rec fold = function
| Empty -> G.empty
| Vertex x -> G.vertex x
| Overlay (x, y) -> G.overlay (fold x) (fold y)
| Connect (x, y) -> G.connect (fold x) (fold y)
in
fold
end
This made me wonder why OCaml had such a limitation. I tried searching around the issue tracker and the forum but could not find any information… then several hours after giving up, I stumbled on this post, which used and type to add secondary type constraints. Indeed, gong back to the documentation it seems like the grammar of the module-type and package-type non-terminals are subtly different! Is there an important reason for this, or is it an oversight? I.e. why isn’t package-type defined as:
package-type ::= modtype-path
- ∣ modtype-path with package-constraint { and package-constraint }
+ ∣ package-type with package-constraint { and package-constraint }
I think this would be much less surprising, if there’s nothing preventing it. Failing that, it would be nice if the error message could prompt you to use and type instead of with type. Something like:
Error: Syntax error: invalid package type: secondary "with type" constraints should use the "and type" syntax
I think there are probably better ways of presenting such and error - see Rust’s diagnostics, for example, but that’s another issue.