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.
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.
yeah I like this idea! :thumbs_up:
I hadn't heard of it before, but I think someone mentioned recently that Zig does the same thing
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(),
)))
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...
Couldn't 3 be
a(b(c(
d(),
)))
The current form is... yeah...
Yeah, the current form is wack... but I'm sure we could take inspiration, while maybe enforcing some additional rules.
I like your version there, Sam. much better.
Glad to hear it! I think they would rather trade terseness for readability
Which I lean the other way on
I think darts style, which often ends up looking kinda like html, works well for writing UIs in flutter, but not great for roc.
That makes sense
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"
But I'm hoping that's a minor enough thing to not be a problem
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:
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()
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
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"
Hmm if people don't know about this rule it would default to single line when they have a habit of not adding the ,
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.
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:
(although I do think that functionality would be useful for docs generation because in that case we're rendering into a known fixed width)
Well that makes things simple! Break on a comma, otherwise keep as is.
I already have a plan for this by leveraging uniformity in parsing and formatting of collections
Ian McLerran said:
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
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!
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])
I believe strongly in "using a trailing comma means I want this to remain multi-line"
Anthony Bullard said:
I already have a plan for this by leveraging uniformity in parsing and formatting of collections
Music to my ears!
I also believe strongly in "no trailing comma means I want this to remain single line" :grinning_face_with_smiling_eyes:
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.
But then when we format, that last item should have a trailing comma
Last updated: Jun 16 2026 at 16:19 UTC