Confusion about code structuring, dune, ocamlformat, ocamldoc (overall workflow)

I am still (somewhat) new to OCaml and I struggle with the overall workflow of creating a project, organizing my code, documenting it, and automatically creating documentation. I would like to share my experiences here and ask a few questions.

I created a project as follows (FreeBSD sh):

$ dune init project demo_project demo_project

I also created an empty .ocamlformat file at demo_project/.ocamlformat.

Then I created two files in demo_project/lib/, which were autoformatted (after running dune fmt) as follows:

demo_project/lib/demo_project.ml:

(** Demo project *)

module type T = Foo.T
(** Some type *)

module M = Foo.M
(** Some module *)

demo_project/lib/foo.ml:

(** Foo *)

(** Foo's module type T *)
module type T = sig
  val hello : unit -> unit
  (** Greeting *)
end

module type T2 = T
(** Foo's module type T2 *)

(** Foo's module M *)
module M = struct
  (** Greet with "Hello!" *)
  let hello () = print_endline "Hello!"
end

When I tried to create documentation of my code using dune build @doc, nothing happened:

jbe@localhost:~/demo_project/lib $ dune build @doc
Entering directory '/home/jbe/demo_project'
Leaving directory '/home/jbe/demo_project'

I searched on the net, and people said I need a public_name for my library, so I modified demo_project/lib/dune as follows:

 (library
- (name demo_project))
+ (name demo_project)
+ (public_name demo_project))

It took me some time to find out I need to run dune build @doc it in a different directory than I did (even though the output indicated that the program found my project root by itself). So the following worked:

jbe@localhost:~/demo_project/lib $ cd ..
jbe@localhost:~/demo_project $ dune build @doc
jbe@localhost:~/demo_project $ 

No output to stdout, but the documentation is created. I don’t understand why.

To add to my confusion, dune build @doc-private seems to build some docs, even if run inside the lib/ directory. What is going on?

But as long as

  • there is a public_name in my lib/dune file, and
  • I run dune build @doc in the right directory

the documentation gets created now.

Another thing I am confused about is the position of the documentation comments (as placed by dune fmt):

  • When does a doc comment appear before, and when after an item?
  • Why is val hello’s doc comment placed after and let hello’s doc comment placed before the corresponding item?
  • Why is module type T’s doc comment placed before and module type T2’s doc comment placed after the respective module type definition? Both are module types.

I wonder: Is it idiomatic to move some of your code in a private module (here Foo, for example) and then import/re-export specific items? I would say I’m following the documentation on Library Wrapper Modules here, so I guess it is reasonable to do. This re-exporting also seems to be necessary in some cases when you want to strucuture your code in different files, because, for example, it’s not possible to place a functor directly in an own file.

Regading modules and re-exports, I think this also touches my yet-unanswered question from a different thread:

Should I have modules that I name Public_name_foo instead of my internal module Foo?

I’m sorry my questions are a bit vague. I guess what I want to know is: How do I typically organize my code into public and private modules?

Also, I ran into some issues and questions regarding ocamldoc in that matter:

  • Where do I document my items if I re-export them by the library wrapper module? Note in my code there are comments where I re-export the module type T and module M, and I also have comments inside the lib/foo.ml file, where I define those. Apparently what ocamldoc does is inconsistent:
    • In Module Demo_project, I see the comments of the re-exports. When I follow the link to the module type T, I also see the comment from the re-export (Some type). But when I follow the link to the module M, I see both the comment from the re-export (Some module) and the comment where M was defined in foo.ml (Foo's module M).
  • Why do modules and module types behave differently with regard to which documentation comments show up on a re-export?

Also, when I re-export the module Foo.M as Foo, the links disappear in the documentation:

 (** Demo project *)
 
 module type T = Foo.T
 (** Some type *)
 
-module M = Foo.M
+module Foo = Foo.M
 (** Some module *)

Now after this change, the module type Demo_project.T and the now renamed module Demo_project.Foo (internally Foo.M) both disappear from the documentation. Even though, I can still use them:

utop # Demo_project.Foo.hello ();;
Hello!
- : unit = ()

What is going on? Is this a bug of ocamldoc? Am I allowed to do this re-export or is it not really allowed?

Sorry this is a lot of questions and not as structured. Maybe someone can just point me to the right page with documentation. Or also feel free to ask back, if my questions aren’t clear.

1 Like

Something seems buggy. I could reproduce another weird behavior as follows. Consider a new project initialized with dune init project prj2 prj2:

prj2/lib/dune:

(library
 (name prj2)
 (public_name prj2))

prj2/lib/prj2.ml:

include Submodule

prj2/lib/submodule.ml:

(** Submodule *)

module M = struct end
(** Some comment on M *)

Running dune build @doc from the prj2/ directory results in “Some comment on M” appearing twice in prj2/_build/default/_doc/_html/prj2/Prj2/M/index.html:

Up – Index » prj2 » Prj2 » M

Module Prj2.M

Some comment on M

Some comment on M

Is this a bug, or am I doing something wrong?

I use:

$ dune --version
3.20.2
$ ocamldoc --version
5.4.0