Running the new Elixir formatter

syntax

#42

Is there a place where reasons for formatting choices have been discussed, or an appropriate place to ask questions?

When looking through formatted code there are a few unexpected cases and it would be nice to understand how that choice was made.

This formatted map update syntax in particular is an interesting one:

%{
  document
  | id_counter: id,
    nodes: insert_node(document.nodes, node)
}

instead of

%{document
  | id_counter: id,
    nodes: insert_node(document.nodes, node)
}

#43

I would say it would be weird for a multiline map to look like this:

%{
  id_counter: id,
  nodes: insert_node(document.nodes, node)
}

and then we don’t have a newline after %{ for updates.


#44

That’s reasonable. I had never formatted them like either syntax before, and preferred:

%{document |
  id_counter: id,
  nodes: insert_node(document.nodes, node)}

But if you’re moving the braces onto their own lines I guess the formatter is logically consistent. Looks weird, but I’m sure I’ll get used to it.


#45

I also preferred the previous approach but I found I was in the minority. So you are not the only one getting used to it. :slight_smile:


#46

I just remember seeing (RE: the formatter):

There is actually a counter-example where the development happened behind “closed doors”. This was a deliberate decision because style discussions can be quiet opinionated and heated.

So I assume the discussion exists somewhere- is it ever likely to be made public for the purpose of answering questions like mine?

Also, is formatting types from another module with parens (String.t as String.t()) just a matter of consistency? I think it might be one of the most common changes I’ve seen.


#47

I tend to prefer:

%{document|
  id_counter: id,
  nodes: insert_node(document.nodes, node),
}

There is only ever something after a multi-line map starting token of %{ if it is updating, and that line ends with |, no spaces, then each line is a single key/value pair, always ending with a , for easy moving around and copy/pasting or so, then finally a line with just a } by itself. It is a pattern I’ve used from other languages that have similar map/dict updating styles. ^.^

EDIT: And non-updating syntax I use the same without the part after the %{, like:

%{
  id_counter: id,
  nodes: insert_node(document.nodes, node),
}

#48

Yup, the formatter always add parens to remote calls.


#49

I love Rust’s adoption of the trailing comma for that reason- so much easier to modify. It never seemed particularly idiomatic to put it in Elixir code, though.


#50

This one made me chuckle (because parens are usually added).

defp not_(b) when is_boolean(b), do: not(b)

reformated to

defp not_(b) when is_boolean(b), do: not b

I guess not has special formatting?


#51

not is a unary operator, so it follows the logic of other unary operators (which is no parens). For instance, you write +foo, not +(foo).


#52

Maybe not always a huge win for readability.

Before:

@document Meeseeks.Parser.parse(
  {"html", [], [
     {"head", [], []},
      {"body", [], [
          {"div", [], [
              {"p", [], ["1"]},
              {"p", [], ["2"]},
              {"div", [], [
                  {"p", [], ["3"]},
                  {"p", [], ["4"]}]},
              {"p", [], ["5"]}]}]}]})

After:

@document Meeseeks.Parser.parse({
            "html",
            [],
            [
              {"head", [], []},
              {
                "body",
                [],
                [
                  {
                    "div",
                    [],
                    [
                      {"p", [], ["1"]},
                      {"p", [], ["2"]},
                      {"div", [], [{"p", [], ["3"]}, {"p", [], ["4"]}]},
                      {"p", [], ["5"]}
                    ]
                  }
                ]
              }
            ]
          })

Edit: Just want to clarify I’m not trying to be negative about the formatter- I love it! I think it will help the community greatly. A big thank you to those people involved in working on it.


#53

Yeah I have a lot of these cases too. Really wish there was a way to turn off auto-formatting for blocks then turn back on after…


#54

Oh yeah, I totally agree it is worse in this case. Tuples feel awkward when expanding one per line, maybe we could do something about it. Can you please open up issue?

If you could provide examples it would be helpful. For instance, seeing the example above makes it clear the code is worst regardless of taste.


#55

Here was one from one of my work projects (a port from one of my old erlang libraries with some more features):
Before the formatter:

 def permutations do
    {
      151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, # Each row defines a dimension
      36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120,
      234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,
      88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71,
      134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133,
      230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161,
      1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130,
      116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250,
      124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227,
      47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44,
      154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98,
      108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34,
      242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14,
      239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121,
      50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243,
      141, 128, 195, 78, 66, 215, 61, 156, 180, 151, 160, 137, 91, 90, 15, 131,
      13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37,
      240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252,
      219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125,
      136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158,
      231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245,
      40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187,
      208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198,
      173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126,
      255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223,
      183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167,
      43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185,
      112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179,
      162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199,
      106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236,
      5, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156,
      180, # Trailing value as a catch-all
    }
  end

And post-formatter:

  def permutations do
    {
      # Each row defines a dimension
      151,
      160,
      137,
      91,
      90,
      15,
      131,
      13,
      201,
      95,
      96,
      53,
      194,
      233,
      7,
      225,
      140,
      36,
      103,
      30,
      69,
      142,
      8,
      99,
      37,
      240,
      21,
      10,
      23,
      190,
      6,
      148,
      247,
      120,
      234,
      75,
      0,
      26,
      197,
      62,
      94,
      252,
      219,
      203,
      117,
      35,
      11,
      32,
      57,
      177,
      33,
      88,
      237,
      149,
      56,
      87,
      174,
      20,
      125,
      136,
      171,
      168,
      68,
      175,
      74,
      165,
      71,
      134,
      139,
      48,
      27,
      166,
      77,
      146,
      158,
      231,
      83,
      111,
      229,
      122,
      60,
      211,
      133,
      230,
      220,
      105,
      92,
      41,
      55,
      46,
      245,
      40,
      244,
      102,
      143,
      54,
      65,
      25,
      63,
      161,
      1,
      216,
      80,
      73,
      209,
      76,
      132,
      187,
      208,
      89,
      18,
      169,
      200,
      196,
      135,
      130,
      116,
      188,
      159,
      86,
      164,
      100,
      109,
      198,
      173,
      186,
      3,
      64,
      52,
      217,
      226,
      250,
      124,
      123,
      5,
      202,
      38,
      147,
      118,
      126,
      255,
      82,
      85,
      212,
      207,
      206,
      59,
      227,
      47,
      16,
      58,
      17,
      182,
      189,
      28,
      42,
      223,
      183,
      170,
      213,
      119,
      248,
      152,
      2,
      44,
      154,
      163,
      70,
      221,
      153,
      101,
      155,
      167,
      43,
      172,
      9,
      129,
      22,
      39,
      253,
      19,
      98,
      108,
      110,
      79,
      113,
      224,
      232,
      178,
      185,
      112,
      104,
      218,
      246,
      97,
      228,
      251,
      34,
      242,
      193,
      238,
      210,
      144,
      12,
      191,
      179,
      162,
      241,
      81,
      51,
      145,
      235,
      249,
      14,
      239,
      107,
      49,
      192,
      214,
      31,
      181,
      199,
      106,
      157,
      184,
      84,
      204,
      176,
      115,
      121,
      50,
      45,
      127,
      4,
      150,
      254,
      138,
      236,
      205,
      93,
      222,
      114,
      67,
      29,
      24,
      72,
      243,
      141,
      128,
      195,
      78,
      66,
      215,
      61,
      156,
      180,
      151,
      160,
      137,
      91,
      90,
      15,
      131,
      13,
      201,
      95,
      96,
      53,
      194,
      233,
      7,
      225,
      140,
      36,
      103,
      30,
      69,
      142,
      8,
      99,
      37,
      240,
      21,
      10,
      23,
      190,
      6,
      148,
      247,
      120,
      234,
      75,
      0,
      26,
      197,
      62,
      94,
      252,
      219,
      203,
      117,
      35,
      11,
      32,
      57,
      177,
      33,
      88,
      237,
      149,
      56,
      87,
      174,
      20,
      125,
      136,
      171,
      168,
      68,
      175,
      74,
      165,
      71,
      134,
      139,
      48,
      27,
      166,
      77,
      146,
      158,
      231,
      83,
      111,
      229,
      122,
      60,
      211,
      133,
      230,
      220,
      105,
      92,
      41,
      55,
      46,
      245,
      40,
      244,
      102,
      143,
      54,
      65,
      25,
      63,
      161,
      1,
      216,
      80,
      73,
      209,
      76,
      132,
      187,
      208,
      89,
      18,
      169,
      200,
      196,
      135,
      130,
      116,
      188,
      159,
      86,
      164,
      100,
      109,
      198,
      173,
      186,
      3,
      64,
      52,
      217,
      226,
      250,
      124,
      123,
      5,
      202,
      38,
      147,
      118,
      126,
      255,
      82,
      85,
      212,
      207,
      206,
      59,
      227,
      47,
      16,
      58,
      17,
      182,
      189,
      28,
      42,
      223,
      183,
      170,
      213,
      119,
      248,
      152,
      2,
      44,
      154,
      163,
      70,
      221,
      153,
      101,
      155,
      167,
      43,
      172,
      9,
      129,
      22,
      39,
      253,
      19,
      98,
      108,
      110,
      79,
      113,
      224,
      232,
      178,
      185,
      112,
      104,
      218,
      246,
      97,
      228,
      251,
      34,
      242,
      193,
      238,
      210,
      144,
      12,
      191,
      179,
      162,
      241,
      81,
      51,
      145,
      235,
      249,
      14,
      239,
      107,
      49,
      192,
      214,
      31,
      181,
      199,
      106,
      157,
      184,
      84,
      204,
      176,
      115,
      121,
      50,
      45,
      127,
      4,
      150,
      254,
      138,
      236,
      5,
      93,
      222,
      114,
      67,
      29,
      24,
      72,
      243,
      141,
      128,
      195,
      78,
      66,
      215,
      61,
      156,
      # Trailing value as a catch-all
      180
    }
  end

And that is just one of many things in a variety of formats that just utterly visually break when the formatter is ran over them (mostly in this project, math stuff).

So yes, until the formatter supports a way to ignore entire blocks of code I will not be able to use it in quite a few areas… ^.^;


#56

Nearly all kind of tabular data:

@option_spec [
  {"foo",      ?f, "Does a foo",                 :boolean, false},
  {"borbarad", ?b, "Master of dreams and death", :boolean, false},
  {"verbose",  ?v, "Set verbosity level",        :integer, 0}
]

#57

I opened an issue on the formatter hurting readability, if people want to add examples there.

EDIT: Actually, as per José’s request, please don’t add examples of different readability issues there. If you have a similar problem that’s not related to the formatting of tuples, open a different issue


#58

For this particular case, I would build a list using ++ and then convert it to a tuple.

Yes, tabular data will be lost by design because it almost always forces unrelated changes when adding larger entries.


#59

I’m going to ask for something really stupid, but which makes sense and I’ll regret it for the rest of my life if I don’t ask it now: if there are going to be “special” comments in Elixir (to direct the formatter, for example), make them so that a non-whitespace character follows the # character. That is, do this:

#! special

Instead of this

# !special

If such comments could be standardized on a single symbol, like !, it would be even better.

I’m asking this because of my “inverse literate programming tool”, which treats comments as text and could render comments like #! special stuff as literal comments instead. I know my library is used by exactly on person (me), but it was released 5 hours ago and I think that people who work with Elixir source might be happy about a standardized format for such comments.


#60

So why did you remove trailing comma then?

I do accept your decission this way or the other, but I’d be happy about a magic comment that lets me turn of the formatter for a couple of lines, because for matter hurts more here than it helps.


#61

Except I’m baking it into the module at compile-time for speed reasons (it really helps in these cases).
I guess I could unquote it inline, but doing multiple lists, concat’ing them, then turning (piping) into a tuple, all within an unquote seems exceedingly and almost stupidly verbose and will make me look over the code as Wth is going on here?!? every time I look at it when I’ve forgotten about it, that should just not be done… o.O!

A lot of my things in the same project are tabular as well, some math matrices, and the formatter fubars them to heck as well.

I have no clue what should be done… ^.^;

Comments is pretty common in the C++ world to control the formatter, I bet it is elsewhere too. But I could see module definitions be used as well though that would constrain you to the module level which is not so good…

I don’t really care how it gets defined to skip, as long as it is not something irritating like a configuration file that let’s you skip a set of lines that would inevitably get out of sync over time… ^.^;

Yeah I agree as well. Multi-line lists/tuples/maps/etc… should *always*always*always* keep the trailing comma to allow for easy sorting and moving of lines at the very least (plus consistency!).