I propose that we have the formatter add a space in front of the . in static-dispatch function calls (and the -> in local-dispatch ones).
# current
foo.bar.baz(Aaaaaa.bbbbbb, Aaaaaa.cccccc()).qux(ddd.eee, ddd.fff, ddd.ggg)
# proposal
foo.bar .baz(Aaaaaa.bbbbbb, Aaaaaa.cccccc()) .qux(ddd.eee, ddd.fff, ddd.ggg)
Our current approach to whitespace and periods seems to be that only a newline prefix is optional for readability:
foo.bar
.baz(Aaaaaa.bbbbbb, Aaaaaa.cccccc())
.qux(ddd.eee, ddd.fff, ddd.ggg)
However, I'm concerned about the readability of the dense one-line chains that we may be encouraging (that I already see regularly in examples that folks post in here).
Here's an example from roc-realworld (with the first line modified to use local function dispatch, though that could improve all these lines):
# current
auth! = |handle!| Auth.authenticate(req, now!()).and_then!(handle!)->to_resp
auth_optional! = |handle!| to_resp(Auth.auth_optional(req, now!()).and_then!(handle!))
from_json! = |handle!| to_resp(req.body().decode(Json.utf8).and_then!(handle!))
# proposal
auth! = |handle!| Auth.authenticate(req, now!()) .and_then!(handle!) ->to_resp
auth_optional! = |handle!| to_resp(Auth.auth_optional(req, now!()) .and_then!(handle!))
from_json! = |handle!| to_resp(req .body() .decode(Json.utf8) .and_then!(handle!))
(see my branch with these spaces added to all dispatches)
To me, this
While this would surely consume a little of our strangeness budget, it seems intuitive and worth it! What do you think?
I'm concerned about the readability of the dense one-line chains
I share the concern, the space before does look weird to me though. I would rather force more newlines, but Richard does not like that :p
I understand Richard's concern for fighting with the formatter, but allowing the kind of density like in Jan's auth snippet (before) would be a big mistake in my opinion.
I just now scrolled up through all #ideas posts in February, and I was surprised to see basically zero multi-dispatch one-liners to quote+spaceify here. I suppose the offensively-dense ones I'm remembering were from earlier.
I did see this, though, which has become almost a must-include in topics proposing changes related to dispatch syntax:
# current
4->hours->ago!()
# proposal
4 ->hours ->ago!()
The roc-realworld example seems much easier to improve in a non-pioneering way with newlines instead of space-separated method calls
"pioneering" meaning the exploration of a syntax that I've not seen in another language before
# with newlines instead (idk if the braces are required)
auth! = |handle!| {
Auth.authenticate(req, now!())
.and_then!(handle!)
->to_resp
}
auth_optional! = |handle!| {
to_resp(
Auth.auth_optional(req, now!())
.and_then!(handle!)
)
}
from_json! = |handle!| {
to_resp(
req
.body()
.decode(Json.utf8)
.and_then!(handle!)
)
}
I meant between the methods
Yeah I misclicked enter :P
That's extremely readable to me, I think the question would then be "when do we force newlines"
Because I think with short method names, three calls in a row can be totally reasonable:
build_color = |r, g, b| color().r(r).g(g).b(b)
vs
build_color = |r, g, b| color() .r(r) .g(g) .b(b)
or (added after Sam's next message)
build_color = |r, g, b|
color()
.r(r)
.g(g)
.b(b)
Totally subjectively, it seems like a minor readability improvement for a marked (no one else does this) weirdness cost
I agree it's not critical for short one-liners. My concern is mostly when commas and/or nesting are involved, which has subjectively felt frequent.
I share the same concern
I would need strong convincing that we can't solve this problem with the existing solution of newlines
But in general, Roc takes the learning from Elm that you can communicate control flow rough shape with newlines and tab depth
And those method chains are just Dark Bramble
Would you prefer the formatter enforce a newline prefix with dispatch, at least for "longer" chains like in the realworld quote above?
Yes, for . and for ->
Would that solve your concern if we found the magic rule of when to apply that?
I would
JanCVanB said:
# before auth! = |handle!| Auth.authenticate(req, now!()).and_then!(handle!)->to_resp auth_optional! = |handle!| to_resp(Auth.auth_optional(req, now!()).and_then!(handle!)) from_json! = |handle!| to_resp(req.body().decode(Json.utf8).and_then!(handle!)) # after auth! = |handle!| Auth.authenticate(req, now!()) .and_then!(handle!) ->to_resp auth_optional! = |handle!| to_resp(Auth.auth_optional(req, now!()) .and_then!(handle!)) from_json! = |handle!| to_resp(req .body() .decode(Json.utf8) .and_then!(handle!))
I'd say that the spaces don't help me de-kudzu this example, but maybe it's readable to you? I think newlines are necessary for readability here
One upside of static dispatch that I've seen Richard celebrate on multiple occasions is being able to view more code without scrolling, so I doubt that 4x-ing the LOC count of business logic is actually an option he'd go for. (Though it would only expand dense spots with no decrease in concision.) My goal here was to propose something that doesn't reopen any LOC explosion fears, but that might be a necessary consideration.
In my eyes, density is the root of the problem
So managing density is what's required if that's the case
I'm not sure the formatter should really have an opinion here. These aren't even long chains. I think we should say if there is a newline before any of the accesses, we newline them all. But this example just needs newlines I think for most people.
auth! = |handle!|
Auth.authenticate(req, now!())
.and_then!(handle!)
->to_resp()
auth_optional! = |handle!|
Auth.auth_optional(req, now!())
.and_then!(handle!)
->to_resp()
from_json! = |handle!|
req
.body()
.decode(Json.utf8)
.and_then!(handle!)
->to_resp()
But I'm not convinced the formatter should enforce this.
The above is at least readable and consistent, you can see each step without worrying about creating a block or creating named variables(the real value of a pipeline)
I agree that it'll be very hard to come up with a simple but non-aggravating rule for the formatter to add newlines
Which is why I'd not be in favor unless something like that is found
Clarification: My proposal specifically doesn't include accesses.
(though I get what you're saying about newline consistency, Anthony)
I stand by my proposal as compatible with whatever we decide about newlines, because it's specifically about handling the newline-less cases.
JanCVanB said:
Clarification: My proposal specifically doesn't include accesses.
That would run directly into the precedence issues that Richard had with pizza's interaction with methods right
I think not, because it should still parse unambiguously and it's just for aesthetics, like the spaces we like after commas.
I think the weirdness budget cost on the spaces idea is prohibitively high in this case
but I think it's worth discussing the question of whether the formatter should enforce newlines
in general, we historically have never enforced newlines anywhere
why would this be the first case where we should do that? :thinking:
I believe that with the pizza operator nearly everyone chose to format things over multiple lines but . may not encourage this as much
And I think that is fine. If someone is keeping it oneline, they've deemed that as a readable expression. If we started having a formatter that enforces line lengths (using some variant of A Prettier Printer), I think we could then say easily that individual accesses would be a Group and flow to a newline (with or without a new level of indentation)
If someone is keeping it oneline, they've deemed that as a readable expression.
I think the person who wrote the code can't judge this well, they've got a clear picture of what the code does in their mind. They will quickly believe the code is easy to understand.
Like with function arguments, function params, record fields, lists, and tuples where it is a decision of the user based on a trailing comma, I mostly think this fits as an ok decision of the user based on inserting a new line.
I do agree it is likely to lead to denser code than |>. I think that is pretty obvious to see looking at other languages
I think for anyone looking at the topic using their phone, you will be extra biased to want new lines. I also think that looking at this code with syntax highlighters on Zulip that do not match what roc syntax highlighting will look like will also lead to wanting more new lines.
To me, this really feels like a wait and see kind of issue
Once there are 10s of thousands of lines of roc code on the new syntax, let's get a vibe check on density.
(tangent!) This actually brings up a slight syntax ambiguity. What if you have a function that you want to consist of two lines: (1) a statement, and then (2) returning a record accessor function.
Something like:
foo = |x| {
if x == 3 { return .a }
Stdout.write("Tried to do a thing ${x}")
.b
}
How do we make sure that's not parsed as:
foo = |x| {
if x == 3 { return .a }
Stdout.write("Tried to do a thing ${x}").b
}
great point!
an easy fix would be changing accessor syntax to be _.b
So no space? like xyz.b?
edit: I guess that doesn't work with newlines
I think the same thing also applies to the static-dispatch closures, e.g. the .foo() in .values.map(.foo())
indeed!
Richard Feldman said:
an easy fix would be changing accessor syntax to be
_.b
I was actually assuming we were doing this :rolling_on_the_floor_laughing:
Last updated: Jun 16 2026 at 16:19 UTC