This style doesn’t work very well with merlin unfortunately. The type errors that you get are hideous with the location spanning the entire signature.
This style doesn’t work very well with merlin unfortunately. The type errors that you get are hideous with the location spanning the entire signature.
That’s true, I’ve learnt to work around it by fixing types as soon as
possible, and using assert false or similar things inside the module.
That’s actually another place where annotating functions is useful,
because is restricts the error to inside the function, instead of the
whole module.
Wow, thanks for all the replies!
I can definitely see the arguments for both extremes. For someone like me, who is still new to OCaml, the .mli files have served as a nice set of “training wheels”; I get nicer and earlier errors when my implementation’s type signature doesn’t match what I had in mind. That advantage will probably become less relevant over time.
Since type-based disambiguation has been introduced in OCaml 4.01 (Using well-disciplined type-propagation to disambiguate label and constructor names · Issue #5759 · ocaml/ocaml · GitHub), it is quite common to write code that only type-checks with type annotations.
type t = A | B
type u = A | C
let f (x : t) : u = match x with A -> A | B -> C
More generally, I tend to annotate my let-definitions more and more systematically (at least the top-level ones): I think that this really improves error messages when type inference fails, and it helps for code review and rereading (it can be seen as a form of compiler-checked code documentation).
To answer to the initial question of the topic, I tend to write .mli
files quite systematically, even for the entry-point module of an executable (in which case the .mli
is just an empty file: it enables warnings 32, 34, 37 to detect unused definitions).
I usually write the definition first in .ml
before writing the declaration and the documentation in .mli
, because writing the code often helps me to find the right interface, especially when types become quite complex (with GADTs, typically). But I generally write a .mli
file at least as soon as I have to use a definition from one module in another one.
When I have a lot of types to declare, I prefer to put them in a separate .ml
file, without .mli
, and in this case the .ml
file contains the API documentation that would go otherwise in .mli
files. Note that I could write them in a .mli
file without implementation as well, except that it would prevent me to apply deriving mechanisms on these types, that I often want to use.
Functors are not a particular reason for writing .mli
s, since signature compatibility is checked structurally anyway. On the contrary, module type
s are typically the kind of things I want to write in a .ml
file without .mli
, in order to not having to write them twice. An extreme case of that is when I need to manipulate the signature of a module Module
as a module type: instead of writing the signature in module.mli
and use module type of Module
, I usually prefer to define a module type S
in moduleS.ml
(without moduleS.mli
), and then module.mli
is reduced to include ModuleS.S
.
I think this benefit is worth repeating! It’s a really helpful way to find intentionally and accidentally unused code.
Hmmm… I feel the compiler should detect those unused definitions even without the .mli file.
Thierry is elite level, most people are not like that and don’t know everything he knows about ocaml.
Just noticed this thread. Very interesting to see some very experienced users saying that they don’t usually write mli files. I thought I was weird. For me, writing mli files often would partly defeat the purpose of using OCaml. I don’t want to have to write types in my code, and OCaml doesn’t make me do it. Yet I still get type safety.