Formatting Decision: When with Multiline Guards
March 3, 2021 · View on GitHub
This is a document explaining our reasoning behind the formatting decision for when with multiline guards.
We chose to format when with multiline guards by
using the when and -> as parenthesis and placing the arrow on a newline
as this most closely matched our goals.
Goals:
- Create/keep differentiation between the guards and the body of the function.
- Minimize diff when changing a single line, impacts surrounding context.
- Minimize indentation, as this causes more lines not to fit and more line breaks.
- Consistent indenting by 4, to aid writability
Here you can see all the other candidates we evaluated against our goals.
Newline and double indentation
The first idea was for when to be placed on a new line and doubly-indented.
This worked well, except for the case of extremely small functions and multiline guards (for example, because of a comment):
- ✅ Differentiation between guards and body
- ✅ Minimize diff when changing a single line
- ❌ Minimize indentation
- ❌ Indenting by 4
bar(X)
when is_tuple(X);
% some comment
is_list(X) ->
body.
insert_nested({Field, Meta, Key0, Value0}, Comments0)
when Field =:= map_field_assoc;
Field =:= map_field_exact;
Field =:= record_field;
Field =:= generate;
Field =:= b_generate ->
body.
Newline and single indentation
We tried to indent when with one full indentation.
This is good in for multiline guards, but fails the guard-body distinction check when guard is on a separate line, but single-line
- ❌ Differentiation between guards and body
- ✅ Minimize diff when changing a single line
- ✅ Minimize indentation
- ✅ Indenting by 4
bar(Some, Very, Long, Arguments)
when is_tuple(X); is_list(X) ->
body.
Newline and half indentation
Another idea was to indent when by half the indentation.
This is preserves the differentiation between guards and body.
We find this rather awkward as we don't use half-indentation anywhere else in the formatter.
- ✅ Differentiation between guards and body
- ✅ Minimize diff when changing a single line
- ✅ Minimize indentation
- ❌ Indenting by 4
bar(X)
when is_tuple(X);
% some comment
is_list(X) ->
body.
insert_nested({Field, Meta, Key0, Value0}, Comments0)
when Field =:= map_field_assoc;
Field =:= map_field_exact;
Field =:= record_field;
Field =:= generate;
Field =:= b_generate ->
body.
Newline and no indentation
We tried not to indent when.
This creates relatively little differentiation between guards and body of the function.
- ❌ Differentiation between guards and body
- ✅ Minimize diff when changing a single line
- ✅ Minimize indentation
- ❌ Indenting by 4
bar(X)
when is_tuple(X);
% some comment
is_list(X) ->
body.
Floating guards
Small functions and multiline guards look great with "floating" guards.
- ✅ Differentiation between guards and body
- ❌ Minimize diff when changing a single line
- ❌ Minimize indentation
- ❌ Indenting by 4
bar(X) when is_tuple(X);
% some comment
is_list(X) ->
body.
But larger functions with floating guards are debatable.
insert_nested({Field, Meta, Key0, Value0}, Comments0) when Field =:= map_field_assoc;
Field =:= map_field_exact;
Field =:= record_field;
Field =:= generate;
Field =:= b_generate ->
body.
Changing the way we pattern match and argument names is not too uncommon and will then result in a diff of several lines. For example a 5 line diff, instead of a 4 line diff in this case:
insert_nested(KeyValue, Comments0) when Field =:= map_field_assoc;
Field =:= map_field_exact;
Field =:= record_field;
Field =:= generate;
Field =:= b_generate ->
body.
Arrow on newline
We tried to treat when and -> as a sort-of parenthesis.
This preserves clarity, by creating differentiation between guards and body of the function, but has relatively low current usage.
- ✅ Differentiation between guards and body
- ✅ Minimize diff when changing a single line
- ✅ Minimize indentation
- ✅ Indenting by 4
bar(X) when
is_tuple(X);
% some comment
is_list(X)
->
body.
It also works great for longer function declarations.
insert_nested({Field, Meta, Key0, Value0}, Comments0) when
Field =:= map_field_assoc;
Field =:= map_field_exact;
Field =:= record_field;
Field =:= generate;
Field =:= b_generate
->
body.
Newline after when
We tried to bring back the -> onto the same line for a more compact representation, compared to Arrow on newline,
but this loses the differentiation between guards and body.
- ❌ Differentiation between guards and body
- ✅ Minimize diff when changing a single line
- ✅ Minimize indentation
- ✅ Indenting by 4
bar(X) when
is_tuple(X);
% some comment
is_list(X) ->
body.
Body next to arrow
We tried to bring back the body onto the same line for a more compact representation, compared to Arrow on newline,
but this also loses the differentiation between guards and body.
- ❌ Differentiation between guards and body
- ✅ Minimize diff when changing a single line
- ✅ Minimize indentation
- ✅ Indenting by 4
bar(X) when
is_tuple(X);
% some comment
is_list(X)
-> body.
When on same line as guards
We can move when to the same line as guards and still keep the -> on a separate line.
This creates an even greater differentiation between guards and body, than when only the -> is on a separate line.
We find this rather awkward to type when we have multiline guards:
- ✅ Differentiation between guards and body
- ✅ Minimize diff when changing a single line
- ❌ Minimize indentation
- ❌ Indenting by 4
post({Generate, _L, _Pattern, _Expr}, St, expr)
when Generate =:= generate; Generate =:= b_generate
->
body.
bar(X)
when is_tuple(X);
% some comment
is_list(X)
->
body.
insert_nested({Field, Meta, Key0, Value0}, Comments0)
when Field =:= map_field_assoc;
Field =:= map_field_exact;
Field =:= record_field;
Field =:= generate;
Field =:= b_generate
->
body.
When indented by 3
We can indent when by 3 to cause constant indentation by 4 for the other multiline guards.
This creates an even greater differentiation between guards and body, than when only the -> is on a separate line.
- ✅ Differentiation between guards and body
- ✅ Minimize diff when changing a single line
- ✅ Minimize indentation
- ✅ Indenting by 4
insert_nested({Field, Meta, Key0, Value0}, Comments0)
when Field =:= map_field_assoc;
Field =:= map_field_exact;
Field =:= record_field;
Field =:= generate;
Field =:= b_generate
->
body.
❌ We don't have a measurable goal that disqualifies this option, but it tends to look rather awkward, in the case for single line guards.
post({Generate, _L, _Pattern, _Expr}, St, expr)
when Generate =:= generate; Generate =:= b_generate
->
body.
bar(X)
when is_tuple(X);
% some comment
is_list(X)
->
body.
References
- Some of the discussion started on github issue 20.
- This insert_nested function was found in erlfmt.