Stream: ideas

Topic: formatting space in static dispatch


view this post on Zulip jan kili (Mar 01 2025 at 10:10):

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?

view this post on Zulip Anton (Mar 01 2025 at 10:19):

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

view this post on Zulip Anton (Mar 01 2025 at 10:22):

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.

view this post on Zulip jan kili (Mar 01 2025 at 10:48):

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.

view this post on Zulip jan kili (Mar 01 2025 at 10:51):

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!()

view this post on Zulip Sam Mohr (Mar 01 2025 at 10:53):

The roc-realworld example seems much easier to improve in a non-pioneering way with newlines instead of space-separated method calls

view this post on Zulip Sam Mohr (Mar 01 2025 at 10:53):

"pioneering" meaning the exploration of a syntax that I've not seen in another language before

view this post on Zulip jan kili (Mar 01 2025 at 10:57):

    # 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!)
        )
    }

view this post on Zulip Sam Mohr (Mar 01 2025 at 10:57):

I meant between the methods

view this post on Zulip jan kili (Mar 01 2025 at 10:59):

Yeah I misclicked enter :P

view this post on Zulip Sam Mohr (Mar 01 2025 at 11:00):

That's extremely readable to me, I think the question would then be "when do we force newlines"

view this post on Zulip Sam Mohr (Mar 01 2025 at 11:01):

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)

view this post on Zulip jan kili (Mar 01 2025 at 11:03):

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)

view this post on Zulip Sam Mohr (Mar 01 2025 at 11:04):

Totally subjectively, it seems like a minor readability improvement for a marked (no one else does this) weirdness cost

view this post on Zulip jan kili (Mar 01 2025 at 11:05):

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.

view this post on Zulip Sam Mohr (Mar 01 2025 at 11:06):

I share the same concern

view this post on Zulip Sam Mohr (Mar 01 2025 at 11:06):

I would need strong convincing that we can't solve this problem with the existing solution of newlines

view this post on Zulip Sam Mohr (Mar 01 2025 at 11:07):

But in general, Roc takes the learning from Elm that you can communicate control flow rough shape with newlines and tab depth

view this post on Zulip Sam Mohr (Mar 01 2025 at 11:08):

And those method chains are just Dark Bramble

view this post on Zulip jan kili (Mar 01 2025 at 11:09):

Would you prefer the formatter enforce a newline prefix with dispatch, at least for "longer" chains like in the realworld quote above?

view this post on Zulip Sam Mohr (Mar 01 2025 at 11:10):

Yes, for . and for ->

view this post on Zulip Sam Mohr (Mar 01 2025 at 11:11):

Would that solve your concern if we found the magic rule of when to apply that?

view this post on Zulip Sam Mohr (Mar 01 2025 at 11:13):

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

view this post on Zulip jan kili (Mar 01 2025 at 11:24):

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.

view this post on Zulip Sam Mohr (Mar 01 2025 at 11:25):

In my eyes, density is the root of the problem

view this post on Zulip Sam Mohr (Mar 01 2025 at 11:25):

So managing density is what's required if that's the case

view this post on Zulip Anthony Bullard (Mar 01 2025 at 11:52):

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.

view this post on Zulip Anthony Bullard (Mar 01 2025 at 11:54):

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)

view this post on Zulip Sam Mohr (Mar 01 2025 at 12:05):

I agree that it'll be very hard to come up with a simple but non-aggravating rule for the formatter to add newlines

view this post on Zulip Sam Mohr (Mar 01 2025 at 12:05):

Which is why I'd not be in favor unless something like that is found

view this post on Zulip jan kili (Mar 01 2025 at 12:06):

Clarification: My proposal specifically doesn't include accesses.

view this post on Zulip jan kili (Mar 01 2025 at 12:07):

(though I get what you're saying about newline consistency, Anthony)

view this post on Zulip jan kili (Mar 01 2025 at 12:10):

I stand by my proposal as compatible with whatever we decide about newlines, because it's specifically about handling the newline-less cases.

view this post on Zulip Sam Mohr (Mar 01 2025 at 12:12):

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

view this post on Zulip jan kili (Mar 01 2025 at 12:14):

I think not, because it should still parse unambiguously and it's just for aesthetics, like the spaces we like after commas.

view this post on Zulip Richard Feldman (Mar 01 2025 at 12:27):

I think the weirdness budget cost on the spaces idea is prohibitively high in this case

view this post on Zulip Richard Feldman (Mar 01 2025 at 12:28):

but I think it's worth discussing the question of whether the formatter should enforce newlines

view this post on Zulip Richard Feldman (Mar 01 2025 at 12:28):

in general, we historically have never enforced newlines anywhere

view this post on Zulip Richard Feldman (Mar 01 2025 at 12:28):

why would this be the first case where we should do that? :thinking:

view this post on Zulip Anton (Mar 01 2025 at 12:35):

I believe that with the pizza operator nearly everyone chose to format things over multiple lines but . may not encourage this as much

view this post on Zulip Anthony Bullard (Mar 01 2025 at 14:23):

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)

view this post on Zulip Anton (Mar 01 2025 at 16:28):

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.

view this post on Zulip Brendan Hansknecht (Mar 01 2025 at 16:58):

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.

view this post on Zulip Brendan Hansknecht (Mar 01 2025 at 16:59):

I do agree it is likely to lead to denser code than |>. I think that is pretty obvious to see looking at other languages

view this post on Zulip Brendan Hansknecht (Mar 01 2025 at 17:02):

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.

view this post on Zulip Brendan Hansknecht (Mar 01 2025 at 17:02):

To me, this really feels like a wait and see kind of issue

view this post on Zulip Brendan Hansknecht (Mar 01 2025 at 17:03):

Once there are 10s of thousands of lines of roc code on the new syntax, let's get a vibe check on density.

view this post on Zulip Joshua Warner (Mar 01 2025 at 22:51):

(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
}

view this post on Zulip Richard Feldman (Mar 01 2025 at 22:58):

great point!

view this post on Zulip Richard Feldman (Mar 01 2025 at 22:58):

an easy fix would be changing accessor syntax to be _.b

view this post on Zulip Luke Boswell (Mar 01 2025 at 23:16):

So no space? like xyz.b?

edit: I guess that doesn't work with newlines

view this post on Zulip Joshua Warner (Mar 02 2025 at 00:09):

I think the same thing also applies to the static-dispatch closures, e.g. the .foo() in .values.map(.foo())

view this post on Zulip Richard Feldman (Mar 02 2025 at 00:25):

indeed!

view this post on Zulip Anthony Bullard (Mar 02 2025 at 11:48):

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