2.5. Documentation#
OCaml provides a tool called OCamldoc that works a lot like Java’s Javadoc tool: it extracts specially formatted comments from source code and renders them as HTML, making it easy for programmers to read documentation.
2.5.1. How to Document#
Here’s an example of an OCamldoc comment:
(** [sum lst] is the sum of the elements of [lst]. *)
let rec sum lst = ...
The double asterisk is what causes the comment to be recognized as an OCamldoc comment.
The square brackets around parts of the comment mean that those parts should be rendered in HTML as
typewriter font
rather than the regular font.
Also like Javadoc, OCamldoc supports documentation tags, such as @author
,
@deprecated
, @param
, @return
, etc. For example, in the first line of most
programming assignments, we ask you to complete a comment like this:
(** @author Your Name (your netid) *)
For the full range of possible markup inside a OCamldoc comment, see the OCamldoc manual. But what we’ve covered here is good enough for most documentation that you’ll need to write.
2.5.2. What to Document#
The documentation style we favor in this book resembles that of the OCaml
standard library: concise and declarative. As an example, let’s revisit the
documentation of sum
:
(** [sum lst] is the sum of the elements of [lst]. *)
let rec sum lst = ...
That comment starts with sum lst
, which is an example application of the
function to an argument. The comment continues with the word “is”, thus
declaratively describing the result of the application. (The word “returns”
could be used instead, but “is” emphasizes the mathematical nature of the
function.) That description uses the name of the argument, lst
, to explain the
result.
Note how there is no need to add tags to redundantly describe parameters or return values, as is often done with Javadoc. Everything that needs to be said has already been said. We strongly discourage documentation like the following:
(** Sum a list.
@param lst The list to be summed.
@return The sum of the list. *)
let rec sum lst = ...
That poor documentation takes three needlessly hard-to-read lines to say the same thing as the limpid one-line version.
There is one way we might improve the documentation we have so far, which is to explicitly state what happens with empty lists:
(** [sum lst] is the sum of the elements of [lst].
The sum of an empty list is 0. *)
let rec sum lst = ...
2.5.3. Preconditions and Postconditions#
Here are a few more examples of comments written in the style we favor.
(** [lowercase_ascii c] is the lowercase ASCII equivalent of
character [c]. *)
(** [index s c] is the index of the first occurrence of
character [c] in string [s]. Raises: [Not_found]
if [c] does not occur in [s]. *)
(** [random_int bound] is a random integer between 0 (inclusive)
and [bound] (exclusive). Requires: [bound] is greater than 0
and less than 2^30. *)
The documentation of index
specifies that the function raises an exception, as
well as what that exception is and the condition under which it is raised. (We
will cover exceptions in more detail in the next chapter.) The documentation of
random_int
specifies that the function’s argument must satisfy a condition.
In previous courses, you were exposed to the ideas of preconditions and postconditions. A precondition is something that must be true before some section of code; and a postcondition, after.
The “Requires” clause above in the documentation of random_int
is a kind of
precondition. It says that the client of the random_int
function is
responsible for guaranteeing something about the value of bound
. Likewise, the
first sentence of that same documentation is a kind of postcondition. It
guarantees something about the value returned by the function.
The “Raises” clause in the documentation of index
is another kind of
postcondition. It guarantees that the function raises an exception.
Note that the clause is not a precondition, even though it states a condition in
terms of an input.
Note that none of these examples has a “Requires” clause that says something
about the type of an input. If you’re coming from a dynamically-typed language,
like Python, this could be a surprise. Python programmers frequently document
preconditions regarding the types of function inputs. OCaml programmers,
however, do not. That’s because the compiler itself does the type checking to
ensure that you never pass a value of the wrong type to a function. Consider
lowercase_ascii
again: although the English comment helpfully identifies the
type of c
to the reader, the comment does not state a “Requires” clause like
this:
(** [lowercase_ascii c] is the lowercase ASCII equivalent of [c].
Requires: [c] is a character. *)
Such a comment reads as highly unidiomatic to an OCaml programmer, who would
read that comment and be puzzled, perhaps thinking: “Well of course c
is a
character; the compiler will guarantee that. What did the person who wrote that
really mean? Is there something they or I am missing?”