Do you think it’s a major weakness of ocaml that functions, types, and modules cannot be defined recursively without explicitly stating the recursion? Does it get in the way often? Just wanting to hear thoughts for no reason in particular.
In my experience it’s not too bad, but one especially annoying case is with recursive modules (you need to explicitly give them signatures if defining anything more than a type with no ppx derivers)
Not a “major” weakness but yes it does get in the way very often when you write real applications (and yes recursive modules are a pain to define, very unDRY[1]).
However in my opinion (and oddly enough) it can have benefits too because the solutions to work around which usually revolve around carefully organizing and thinking your definitions into subsequent layers tend to produce, in the end, code that is less tangled and easier to evolve and work with.
Usually it’s better to confine and hide the recursive definition to the minimal you need and continue the definition of your module afterwards with type equalities on the tight recursive definition. ↩︎
Indeed. Those are only really needed when your recursive definition involves the result of functor application see the motivating example in the manual.
True, but ppx really gets in the way, because you can’t derive things without an explicit signature. In the past I’ve just defined stuff first and then made modules afterwards to export those things in a nicer way.
It can be a little bit of a pain sometimes but I’ve also found that it tends to lead to better code quality, at least in my experience.
Something I’ve been thinking about is using polymorphic types so I can have type definitions in different files without long chains of and but I don’t like the loss in explicitness so haven’t committed to it.
(I mean something like this, where todo and note can refer to each other.)
type 'note todo = { time: float; note: 'note };;
type 'todo note = { text: string; todo: 'todo option };;
(* Inner record below is a note but outer record is a todo.
Keep in mind that todo was defined before note but that this todo contains a note. *)
let my_todo = { time = 1.5; note = { text = "my_note"; todo = None } };;
I don’t know your exact use-case, but I use a ton of ppx and it works fine with separating code from data. Once I get tangled in a module import loop, I’ll move my types to a Foo_d module, and import that module from the function-containing module Foo.
The one time I had to use recursive modules was when creating an interpreter. I defined an AST composed of two types, with the second type containing a functor application (Map.Make) with the first type, or something along those lines. I wasn’t using ppx back then, so I’m not sure how viable it is.