rescript-association / reasonml.org

Deprecated in favor of rescript-lang.org
MIT License
125 stars 33 forks source link

API documentation structure, voice & tone #79

Open nikgraf opened 4 years ago

nikgraf commented 4 years ago

I would like to define how the ideal documentation structure, voice and tone for a module & its functions look like.

Ideally this results in a very crisp guide on how to do this. Once this is defined we can focus on aligning all of them. (Aligning them already came up 4 times or so).

If you have ideas or good examples please chime in here as well.

One example for a guide I like is this one: https://package.elm-lang.org/help/documentation-format

baransu commented 4 years ago

It's not exactly documentation format or guideline but in general whole HexDocs (Elixir centralized packages documentation) is a great example.

Example section: https://hexdocs.pm/elixir/writing-documentation.html

jdeisenberg commented 4 years ago

Format for Examples

It would be useful to have a consistent format for code examples.

Types of Examples

Looking through the current documentation, there are several main types of examples. I'm labeling them with letters in parentheses for ease of reference.

First, the ones that give results in a comment:

(a) Belt.List.makeBy(5, i => i); /* [0, 1, 2, 3, 4] */
(b) Belt.Option.getWithDefault(None, "Banana"); /* Banana */
    Belt.Option.getWithDefault(Some("Apple"), "Banana"); /* Apple */
(c) /* returns 12.3 */
    Js.Float.fromString("12.3");
(d) [1, 2, 3]->Belt.List.take(1); /* Some([1]) */

Some examples of this type use Js.log():

(e) /* prints "7.71234e+1" */
   Js.log @@ Js.Float.toExponential(77.1234);

Another major type of example uses equality to show what the result should be:

(f) Js.Option.getWithDefault(1066, Some(15)) == 15;
    Js.Option.getWithDefault(1066, None) == 1066;
(g) let f = (x) => sqrt(float_of_int(x));
    Belt.Result.map(Ok(64), f) == Ok(8.0);

And these examples also have a Js.log() subset:

(h) Js.log(Belt.Float.toInt(1.0) === 1); /* true */
(i) Js.log(Js.String2.fromCharCode(65) == "A");
    Js.log(Js.String2.fromCharCode(0x3c8) == {js|ψ|js});
    Js.log(Js.String2.fromCharCode(0xd55c) == {js|한|js});

There are some examples that don’t provide any output of the expected result:

(j) let maybeGreetWorld = (maybeGreeting: Js.null(string)) =>
    Js.Null.bind(maybeGreeting, [@bs] greeting => greeting ++ " world!");

And some examples that are nearly full programs, such as this example for Js.Re.lastIndex():

(k) let re = [%re "/ab*/g"];
    let str = "abbcdefabh";

    let break = ref(false);
    while (! break^) {
      switch (Js.Re.exec_(re, str)) {
      | Some(result) =>
        Js.Nullable.iter(
          Js.Re.captures(result)[0],
          [@bs] match => {
            let next = string_of_int(Js.Re.lastIndex(re));
            Js.log("Found " ++ match ++ ". Next match starts at " ++ next);
          },
        )
      | None => break := true
      };
    };

Discussion

[This is all opinion. Let me know in responses what you think.] First, I would strongly suggest that we not use examples like (k). Although it’s very complete, there’s too much information competing with the function we want to explain.

Example (j) doesn’t give the expected output, which might leave the reader in doubt as to what the result should be.

Examples (e), (h), and (i) are nice if you want to put the example into a program, run it, and see visible results. I am pretty sure I wrote example (i) for that purpose. The problem is that all you see when you run the program in (h) and (i) is a list of true and false results, which isn’t particularly useful. The use of @@ in example (e) is convenient but needs extra explanation. Example (h) uses === and (i) uses ==; we will have to make some decision about which one to use.

Comparing examples (a), (b), (c), and (d) with examples (f) and (g): it seems that (f) and (g) are more amenable to automated testing. Note the use of pipe first -> in example (d). Again, it’s convenient, but might need extra explanation.

Using Js.log()

While Js.log() is satisfying because it produces visible output, there are problems when you have examples like these:

Js.log(Js.List.cons(1, [2, 3, 4, 5])); // [1, 2, 3, 4, 5]
Js.log(Js.Option.map((. x) => (x * x), Some(3))); // Some(9)
Js.log(Js.Option.map((. x) => (x * x), None)); // None

You get output like this, which will confuse new developers unless you explain why it doesn’t look like what they expect:

[ 1, [ 2, [ 3, [Array] ] ] ] 
9
undefined