Stream: ideas

Topic: Code format to single/multiline by trailing comma


view this post on Zulip Ian McLerran (Jan 10 2025 at 18:02):

Dart, like Roc is opinionated on code formatting, and also has a built in dart format command. This is great for ensuring clean consistent looking code as opposed to a hodgepodge divergent of styles, thus making it easier to quickly dive into and read a code base.

I love roc format much like I love dart format. However, one feature I would love to see brought over from dart's formatter is formatting based on trailing commas. In dart, anything that can be separated by commas, such as lists, records, function arguments, etc will be either formatted as a single line, or broken into multiple lines based on whether the last element has a trailing comma. If there is no trailing comma, use a single line. If there is, use multiline formatting.

This gives the author some choice in whether they want fewer lines or shorter lines of code. Sometimes it makes more sense to break things into multiple lines for readability, but not always. The formatter may not always know what is most readable for a human, so allowing a human to have some input can be beneficial, while also allowing deterministic code formatting.

In roc, this could also apply to anything that is comma separated - lists, tuples, records, function arguments, tag unions, etc. So for example:

list_1 = [1, 2, 3]

list_2 = [
    "This is a super long string full of many letters...",
    "This is another super long string full of many characters...",
    "Wow, so many super long strings full of many graphemes...",
]

or:

takes_a_short_func(a, b, |c, d| c + d)

takes_a_long_func(
    arg_1,
    arg_2,
    |arg_alpha, arg_beta|
        something(alpha).transform(beta).operate().do_a_thing_with_a_long_name()
 )

Here is an article on dart's comma based code formatting.

Personally, I think something similar could be a great fit for roc.

view this post on Zulip Sam Mohr (Jan 10 2025 at 18:13):

I'm not sure what the current logic is for Roc, but this makes sense to me. I'm not saying that we should do it, but I do like that this helps convert a multiline function call back and forth to single line by removing a single comma instead of removing lots on newlines as is needed today.

view this post on Zulip Richard Feldman (Jan 10 2025 at 18:30):

yeah I like this idea! :thumbs_up:

view this post on Zulip Richard Feldman (Jan 10 2025 at 18:30):

I hadn't heard of it before, but I think someone mentioned recently that Zig does the same thing

view this post on Zulip Ian McLerran (Jan 10 2025 at 18:36):

It should be noted that this can have some interesting side effects, which we may also want to consider. For example dart's style tends to follow a very ... "pyramidic?" structure, do to this convention, which we may want to consider.

In dart, even a single argument to a function followed by a comma will be newlined. Consider the following:

# 1 --
a(b(c(d())))
# 2 --
a(
    b(
        c(
            d(),
        ),
    ),
)
# 3 --
a(
    b(c(d(),
        )))

view this post on Zulip Ian McLerran (Jan 10 2025 at 18:37):

In dart, # 2 above is good style, and even # 3 is not uncommon, but I think we probably wouldn't want to end up with deep indentations like this...

view this post on Zulip Sam Mohr (Jan 10 2025 at 18:40):

Couldn't 3 be

a(b(c(
    d(),
)))

view this post on Zulip Sam Mohr (Jan 10 2025 at 18:40):

The current form is... yeah...

view this post on Zulip Ian McLerran (Jan 10 2025 at 18:40):

image.png

view this post on Zulip Ian McLerran (Jan 10 2025 at 18:41):

Yeah, the current form is wack... but I'm sure we could take inspiration, while maybe enforcing some additional rules.

view this post on Zulip Ian McLerran (Jan 10 2025 at 18:41):

I like your version there, Sam. much better.

view this post on Zulip Sam Mohr (Jan 10 2025 at 18:42):

Glad to hear it! I think they would rather trade terseness for readability

view this post on Zulip Sam Mohr (Jan 10 2025 at 18:42):

Which I lean the other way on

view this post on Zulip Ian McLerran (Jan 10 2025 at 18:43):

I think darts style, which often ends up looking kinda like html, works well for writing UIs in flutter, but not great for roc.

view this post on Zulip Sam Mohr (Jan 10 2025 at 18:43):

That makes sense

view this post on Zulip Sam Mohr (Jan 10 2025 at 18:49):

The problem with this is that it's not an option to do this, but a requirement. We can't default to breaking up lines to keep code short because formatting code is a "stateless" operation and we can't say "if the formatter broke up the lines, do this, otherwise do this"

view this post on Zulip Sam Mohr (Jan 10 2025 at 18:49):

But I'm hoping that's a minor enough thing to not be a problem

view this post on Zulip Ian McLerran (Jan 10 2025 at 18:53):

Sam Mohr said:

Couldn't 3 be

a(b(c(
    d(),
)))

Just realizing not only could it be, that's what dart would do too. I totally miss formatted that. :sweat_smile:

view this post on Zulip Anton (Jan 10 2025 at 19:02):

This is super long:

something(alpha).transform(beta).operate().do_a_thing_with_a_long_name()

I would always want my formatter to convert that to multiline:

something(alpha)
.transform(beta)
.operate()
.do_a_thing_with_a_long_name()

view this post on Zulip Sam Mohr (Jan 10 2025 at 19:03):

It should be minor because modern editors make it really easy to jump around and fix this, and also most code in parentheses will either be short, or complex with multiline arguments that will force multiline anyway

view this post on Zulip Sam Mohr (Jan 10 2025 at 19:04):

I think we could make the rule "if it needs to be multiline, force multiline and always put the trailing comma. if it doesn't need multiline, comma makes it multiline, removing the comma makes it single line"

view this post on Zulip Anton (Jan 10 2025 at 19:07):

Hmm if people don't know about this rule it would default to single line when they have a habit of not adding the ,

view this post on Zulip Ian McLerran (Jan 10 2025 at 19:08):

Yeah, I think that makes sense. Generally I completely agree that I want long call chains to be broken up by the formatter. But I think there is probably a small grey area where its a matter of preference - maybe relax the formatter's preference on what the max length is slightly, but then if it exceeds max length, insert the comma and break.

view this post on Zulip Richard Feldman (Jan 10 2025 at 19:10):

I forget which thread I mentioned this in previously, but I don't think roc format should ever take line length into consideration :big_smile:

view this post on Zulip Richard Feldman (Jan 10 2025 at 19:10):

(although I do think that functionality would be useful for docs generation because in that case we're rendering into a known fixed width)

view this post on Zulip Ian McLerran (Jan 10 2025 at 19:14):

Well that makes things simple! Break on a comma, otherwise keep as is.

view this post on Zulip Anthony Bullard (Jan 10 2025 at 21:24):

I already have a plan for this by leveraging uniformity in parsing and formatting of collections

view this post on Zulip Anthony Bullard (Jan 10 2025 at 21:26):

Ian McLerran said:

image.png

I really hate the new Dart/Flutter style in some ways. It does make the code less vertical, but I really loved the inlay hints and this kind of makes them hard to parse

view this post on Zulip Ian McLerran (Jan 10 2025 at 21:31):

The inlay hints are amazing. I haven't written much flutter/dart in a couple years, so I'm not sure what the new style is - am I gathering that no trailing comma has become the norm, thus collapsing the trailing parens like the image I posted? I always found that for flutter, I really like the expanded pyramid, particularly with the inlays - makes the code so easy to read!

view this post on Zulip Ian McLerran (Jan 10 2025 at 21:52):

Great example of why trailing commas for formatting would be great, using the current formatter:

     when model.state is
-        ConfirmPage _ ->
-           List.join [
-               confirmScreen model,
-               debug,
-            ]
+        ConfirmPage(_) ->
+            List.join(
+                [
+                    confirm_screen(model),
+                    debug,
+                ],
+            )

         _ ->
-           List.join [
-                homeScreen model,
-                debug,
-            ]
+            List.join(
+                [
+                    home_screen(model),
+                    debug,
+                ],
+            )

Using trailing commas (or lack thereof), this could be any of:

# trailing comma on list and arguments
when model.state is
    ConfirmPage(_) ->
        List.join(
            [
                confirm_screen(model),
                debug,
            ],
        )
# trailing comma on list
when model.state is
    ConfirmPage(_) ->
        List.join([
            confirm_screen(model),
            debug,
        ])
# trailing comma on arguments
when model.state is
    ConfirmPage(_) ->
        List.join(
            [confirm_screen(model), debug],
        )
# no trailing commas
when model.state is
    ConfirmPage(_) ->
        List.join([confirm_screen(model), debug])

view this post on Zulip Anthony Bullard (Jan 10 2025 at 21:56):

I believe strongly in "using a trailing comma means I want this to remain multi-line"

view this post on Zulip Ian McLerran (Jan 10 2025 at 22:00):

Anthony Bullard said:

I already have a plan for this by leveraging uniformity in parsing and formatting of collections

Music to my ears!

view this post on Zulip Ian McLerran (Jan 10 2025 at 22:01):

I also believe strongly in "no trailing comma means I want this to remain single line" :grinning_face_with_smiling_eyes:

view this post on Zulip Anthony Bullard (Jan 10 2025 at 22:02):

Ian McLerran said:

I also believe strongly in "no trailing comma means I want this to remain single line" :grinning_face_with_smiling_eyes:

This is true, unless there are other things within the collection that are multiline.

view this post on Zulip Anthony Bullard (Jan 10 2025 at 22:02):

But then when we format, that last item should have a trailing comma


Last updated: Jun 16 2026 at 16:19 UTC