When should types (and associated functions) form their own modules?

In the Core ecosystem (and Real World OCaml as a result), there seems to be a trend towards putting types and their strongly associated functions (accessors, constructors, etc) into a singe module, so instead of

type foo = { bar: int; baz: bool };;
let baz_of_foo { baz; _ } = baz;;

we’d generally go for

module Foo = struct
  type t = { bar: int; baz: bool };;
  let baz { baz; _ } = baz;;

(excuse my bad syntax!)

My wondering is where one draws the line between situations where putting a type in its own module (and naming it t, and associated conventions) is a good idea, and where it makes more sense to leave it as a single, directly named type. To me it feels like anything that is a natural abstract data type (and thus should have information hiding, etc) is an obvious candidate for a module, but beyond that it’s not entirely obvious.

Quite often I’ve ended up starting with named types but moved them to modules anyway, as the centralisation of all of the type’s strongly associated functions and bits of functionality—as well as being able to include common signatures—becomes a big win.

Are there in fact any real disadvantages to modularisation, except for verbosity? One thing I worry about is tripping over non-OCaml or non-Core programmers who don’t necessarily know that Foo.t should be read as ‘foo, but with a module surrounding it’. Similar issues with Foo.S for 'signature of things that are a Foo'.

(I realise this is a very subjective topic, but I was wondering what people’s intuition is on when to make the leap.)

There are no real disadvantages to modularisation. One counterexample I can see is when the types are mutually recursive. In that case, it’s often better to keep them together to avoid recursive modules.

1 Like

BTW in which case should we consider using mutually recursive modules? (instead of packing up all in one module).
Concerning @MattWindsor91’s question, my very humble experience is driving me now in creating modules which contain stuff small enough to be reused at the widest scale as possible (in a same program or for all programs). It can be a very specialised module or a very generic module (such as Address).
Could more experienced OCaml programmers give us rules of thumb for that?

When should types (and associated functions) form their own modules?

Always. My rule of thumb is that every type should be called t.

My answer to that would be “never”. Use recursive types or break recursion using parametricity (introduce a type parameter in one of the types).