Docked lists in ocamlformat 0.11

Hi,

I have code formatted by ocamlformat like this:

[ "Something";
  "else";
  "entirely" ]

When I upgrade to ocamlformat 0.11 it reformats it like this:

[ 
  "Something";
  "else";
  "entirely";
 ]

Which makes my diff quite noisy and I would like to keep the behaviour of ocamlformat 0.10. After looking through the documentation, I couldn’t find an option to do so. What is the magic configuration option?

1 Like

I don’t know the answer here but this diff would be one time noise. Everytime you add an element to the list with this new format it would create a smaller diff than the older way the list was formatted.

2 Likes

That is true, though most line noise I have in my codebase is new releases of ocamlformat reformatting things in a different way. Often it is better than before and maybe that is a growing pain on the way to 1.0 but it still makes me wary of updating my ocamlformat version.

1 Like

Hi!

This seems to be related to the break-separators option. It was introduced in 0.9, and even in 0.10 it did not take work for all the OCaml features. It was further expanded in https://github.com/ocaml-ppx/ocamlformat/pull/931, which seems to be causing this diff.

In 0.12 (soon released), this option has been reworked a bit to be more flexible, and the docking aspect is controlled by the new dock-collection-brackets option.

I had a go at formatting orewa with it and tweaked a bit the config, and it looks like this (this is a very small subset of diffs but representative of the issue I think):

diff --git a/.ocamlformat b/.ocamlformat
index f191d20..1e34970 100644
--- a/.ocamlformat
+++ b/.ocamlformat
@@ -17,7 +17,9 @@ sequence-style=terminator
 type-decl=sparse
 wrap-fun-args=false
 module-item-spacing=sparse
-break-separators=after-and-docked
+break-separators=after
+dock-collection-brackets=false
+space-around-lists=false
 break-infix-before-func=false
 break-collection-expressions=fit-or-vertical
 exp-grouping=parens
diff --git a/src/orewa.ml b/src/orewa.ml
index f793210..a2c9941 100644
--- a/src/orewa.ml
+++ b/src/orewa.ml
@@ -262,22 +263,17 @@ let bitfield t ?overflow key ops =
   let ops =
     ops
     |> List.map ~f:(function
-           | Get (size, offset) ->
-               ["GET"; string_of_intsize size; string_of_offset offset]
+           | Get (size, offset) -> ["GET"; string_of_intsize size; string_of_offset offset]
            | Set (size, offset, value) ->
-               [
-                 "SET";
+               [ "SET";
                  string_of_intsize size;
                  string_of_offset offset;
-                 string_of_int value;
-               ]
+                 string_of_int value ]
            | Incrby (size, offset, increment) ->
-               [
-                 "INCRBY";
+               [ "INCRBY";
                  string_of_intsize size;
                  string_of_offset offset;
-                 string_of_int increment;
-               ])
+                 string_of_int increment ])
     |> List.concat
   in
   let overflow =

Now, why does ocamlformat keep changing your code? Put simply, it is pretty hard to find all the interactions between different options - sometimes we find weird interactions between options, and the configuration space is pretty large.

So while 1.0 is not out, we allow changing behaviour when it improves the output: but one’s fix can be another one’s regression, unfortunately. But it seems that we’re making progress!

Anyway, thanks for using ocamlformat and reporting this issue - even though ocamlformat is a couple years old it’s still very much for early adopters so it’s important to get feedback! :pray:

3 Likes

Thanks, indeed these options seem to be making it match the previous output pretty well. One exception seems to be the first hunk of the diff, which seems to be unrelated to the way lists are formatted but rather due to some other change?

This is an issue with most formatters, that the options aren’t really orthogonal but rather influence themselves in some way.

It also does not help that it is difficult to understand the impact of each option from the documentation unless one tries them out on a formatted codebase, since the options have rather unintuitive names and option values (a problem I don’t really know a good solution to, since unlike the OCaml syntax we can’t just agree on what the OCaml parser calls them). As an improvement proposal, maybe it could be possible to include a piece of code that will get affected by said option and have the result of each option documented with an example? That would at least help me as an user to ignore all the options that can’t possibly relate to what I am trying to configure.

Personally I would like to switch to one of the pre-configured profiles eventually (especially if one of them would be palatable to e.g. 80% of OCaml programmers) and there seems to be already a conventional profile. I wonder how the settings for that were chosen?

As a general feedback, I think ocamlformat has been pretty useful at work since it allows us to onboard people faster without having to explain exactly what to do where and stop having to discuss the minutia of how to format things. I also sometime just hack together some change and then just let it reformat that into something readable.

Yes, that is correct, I haven’t checked exactly which one though.

This is one of the problems we’re trying to solve, and there are several ways we could do that. Something like an online playground where you can type code and try options in the browser would be awesome, but we’re not there yet.
Having examples in a manual for each option would be very useful and is probably not too much effort, so we can try to do that - thanks for the suggestion.
Another possibility is to reduce the cost of switching to ocamlformat, for example by having a CI and editor integration which reformats only modified code. That way it would matter less if the new code is not exactly formatted as the existing one. But that’s very much science fiction for now.

We don’t know yet how configuration options will be versioned after 1.0.0, but it’s likely that there will be less breakage when using a profile compared to individual options.

The conventional profile started as a variant of the default profile (as used at Facebook) without the aspects that stood out as unusual to the maintainers, like putting several statements in a sequence on the same line, putting a space before ;, or indicated nested or-patterns with |X rather than | X. It is very much a subjective matter, at least for now. “Being palatable to 80% of OCaml programmers” is a good way to describe its goal.

Thanks for the feedback. That’s how I use it as well, my pre-format code is now a terrible mess.