Convenient way to write return types of OCaml funs in *.ml files?

  1. I understand that idiomatic practice is to put all type signatures in *.mli files and no type sigs in *.ml files.

  2. I’m still getting used to OCaml, so I’m writing type sigs of my defs, i.e.

let foo (x: string) (y: int) = ...
  1. One problem I am running into is: how do I conveniently specify the return type ?

====

The rationale here is I’m still adjusting to OCaml, and I feel specifying more type sigs helps me ‘communicate’ with the type checker and check my understanding.

1 Like

Here are two ways:

let foo (x: string) (y: int) : unit = ...
                               ^^^^ here

or

let foo : string -> int -> unit = fun x y -> ...
                           ^^^^ here

Convenient thing about the second way…if you decide you want to remove the type from the function, you can delete this part : string -> int -> unit, and if you’re using ocamlformat, it will automatically change your function to the “normal” form.

2 Likes

Another advantage of the second form is that you can copy it to the MLI as-is and also it matches the format the type-checker (and LSP/Merlin) use.

I noticed there’s a convenient feature: you can type

let foo : string -> _ -> unit = fun x y -> ...

if you want the type checker to infer the second parameter (e.g. if it is trivial or annoying to type out, like polymorphic variants tend to).

1 Like

Very much a tangent, but I haven’t found this to be the case outside of library projects.

Are folks diligently tending .mli files in their applications? I’ve made a few in that context, but not many (mostly where I’m using a module or two that I expect to make into a library later).

2 Likes

My understanding is that it is a good thing since:

  1. it specifies what is public (and hides all else)
  2. it speeds up type checking (foo.ml only needs parse bar.mli, not bar.ml)

Also, even for most ‘applications’ isn’t the structure often a thin main.ml over a library ? (At least this is what I feel dune projects is pushing me torwards).

Ah, sure, I was speaking loosely. Yes, in dune terminology, outside of the smallest programs, almost everything tends to be in one or another library, and executables are very thin shells over aggregations of them.

I was saying that outside of code I aim to reuse in multiple projects (and/or publish publicly), I’ve not found myself writing .mli files very often.

Eh, if the project I’m working on is not intended for reuse (whether it’s called a library or executable in dune), everything being public is too convenient to bother constraining. Perhaps that wouldn’t be the case if I were on a larger team, but I’ve yet to feel the need to file types in triplicate in the “application” context.

The speed-of-type-checking point is surely strictly correct, but the extra work of being thorough about .mli files (again, in the “application” context) could never be made up by compiler speedups on the order of millis.

I forget where but I remember reading somewhere that Jane Street requires basically every ml file to have a corresponding mli. But I also think it’s good practice to write your whole application as a collection of libraries and have a very small (~100 LOC max) “executable” which does all the bootstrapping for the program. FWIW I try to use either module signatures or mli files almost everywhere as well, but sometimes it doesn’t make sense to IMO.

I currently don’t have any .mli files in a project-in-progress… I think .mlis are a distraction while the project is evolving. OTOH I have a couple CLI submodules which highlight the most “user-facing” functionality (building blocks of a DSL).

1 Like