Stream: announcements

Topic: Breaking change: `a->b()` now formats to `a |> b`


view this post on Zulip Richard Feldman (Jul 31 2026 at 14:17):

We recently discussed introducing a |> operator that works like the current -> binary operator, and deprecating the current a->b syntax so that we can change what it means in the future.

As of this PR we now have the new syntax, and the formatter automatically changes from the old syntax to the new one.

A future PR will change how -> works, but in the meantime, roc fmt should upgrade you.

view this post on Zulip Aurélien Geron (Jul 31 2026 at 23:30):

I just ran the new formatter on all the Exercism exercises, and it worked fine overall, except for one thing: it replaces a->f()? with a |> f? which causes a TYPE MISMATCH error. I've filed issue #10510.

view this post on Zulip Aurélien Geron (Aug 01 2026 at 00:09):

Also, there's weird stuff going on with sequences of pipes. For example, this code:

 expect {
    _result = CircularBuffer.create({ capacity: 3 })
        .write(1)?
        .write(2)?
        .write(3)?
        .read()?
        -> expect_value(1)
        .write(4)?
        .overwrite(5)
        .read()?
        -> expect_value(3)
        .read()?
        -> expect_value(4)
        .read()?
        -> expect_value(5)

    Bool.True
}

main! = |_| { Ok({}) }

Is formatted like this:

expect {
    _result = (
        (
            (
                CircularBuffer.create({ capacity: 3 })
                    .write(1)?
                    .write(2)?
                    .write(3)?
                    .read()?
                    |> expect_value(1),
            ).write(4)?
                .overwrite(5)
                .read()?
                |> expect_value(3),
        ).read()?
            |> expect_value(4),
    ).read()?
        |> expect_value(5)

    Bool.True
}

main! = |_| {
    Ok({})
}

I would have expected something like this:

 expect {
    _result = CircularBuffer.create({ capacity: 3 })
        .write(1)?
        .write(2)?
        .write(3)?
        .read()?
        |> expect_value(1)
        .write(4)?
        .overwrite(5)
        .read()?
        |> expect_value(3)
        .read()?
        |> expect_value(4)
        |> read?
        |> expect_value(5)

    Bool.True
}

main! = |_| {
    Ok({})
}

Should I file a separate issue for this?

view this post on Zulip Aurélien Geron (Aug 01 2026 at 00:19):

Side-note: consider a |> f?. I guess this means (a |> f)?, or equivalently f(a)?. However, what if f is not a function, but it's a Try(_ -> _, _)? In this case, a |> f? should probably mean a |> (f?), or equivalently (f?)(a).
So perhaps a |> f? should always mean (f?)(a), and a |> f()? should always mean f(a)?, this way there's no ambiguity. Consequently, the formatter should not replace a |> f()? with a |> f?, it should leave the parentheses in this case to avoid ambiguity: a |> f()?.
Oh wait, there's still an ambiguity if f() returns a Try(_ -> _, _). I guess in this rare case the user should write a |> (f()?).
Edit: I'm over-complicating things. If f is a Try(_ -> _, _), then the user should just write a |> (f?) explicitly.

view this post on Zulip Richard Feldman (Aug 01 2026 at 02:56):

Should I file a separate issue for this?

@Aurélien Geron yeah please do!

view this post on Zulip Richard Feldman (Aug 01 2026 at 02:58):

Aurélien Geron said:

Side-note: consider a |> f?. I guess this means (a |> f)?, or equivalently f(a)?. However, what if f is not a function, but it's a Try(_ -> _, _)? In this case, a |> f? should probably mean a |> (f?), or equivalently (f?)(a).

that might be a case where () could be load-bearing, e.g.

view this post on Zulip Matthieu Pizenberg (Aug 01 2026 at 10:27):

"load-bearing" spotted

view this post on Zulip Aurélien Geron (Aug 03 2026 at 21:38):

Looking at all exercises, it seems that a |> f? always means (a |> f)?, never a |> (f?). Since a |> (f?) is arguably very rare, I would argue that users should be explicit if that's what they want, so a |> f? should always mean (a |> f)?. I think it's simpler to remember than "a |> f? means a |> (f?) but a |> f()? means (a |> f)?". Moreover, if f() returns a Try(_ -> _, _) then the user might expect a |> f()? to mean a |> (f()?).

view this post on Zulip Richard Feldman (Aug 03 2026 at 21:50):

hm but a |> f? really looks like it's a |> (f?) to me :sweat_smile:

view this post on Zulip Aurélien Geron (Aug 03 2026 at 22:11):

I see your point, but doesn't a |> f()? also look like a |> (f()?).
Definitely not a hill I'd die on. :smile:

view this post on Zulip Richard Feldman (Aug 03 2026 at 22:14):

they both do :laughing:

view this post on Zulip Aurélien Geron (Aug 03 2026 at 22:17):

Yeah, I guess that's the core issue: a |> f? looks like a |> (f?) but (a |> f)? is far more common/useful. Not sure how this can be fixed.

view this post on Zulip Bryce Miller (Aug 03 2026 at 23:04):

Hmm, maybe it’s time to hire a plumber to fix our pipes

view this post on Zulip Bryce Miller (Aug 03 2026 at 23:07):

I’ve heard there’s a way to prevent code smells, but it’s a trap!

view this post on Zulip Arya Elfren (Aug 04 2026 at 08:30):

Richard Feldman said:

they both do :laughing:

Does enforcing (either through error or formatter) that there are either parentheses or a space help? i.e only accepting a |> f ?, a |> f() ?, a |> (f?), and a |> (f()?).

view this post on Zulip Arya Elfren (Aug 04 2026 at 08:30):

Now that I look at it it's not a huge change...

view this post on Zulip Aurélien Geron (Aug 04 2026 at 09:50):

I thought that ? with a space was the binary operator (map_err): isn't that going to cause issues?

I looked at some more code with |> and ? today and I'm actually growing fond of a |> f? to mean (a |> f)?. It's nice and short, and I read it like I read a sentence ending with a question mark: the ? naturally comes last.

view this post on Zulip Arya Elfren (Aug 04 2026 at 10:39):

Aurélien Geron said:

I read it like I read a sentence ending with a question mark

Learn from Spanish: ¿a |> f? vs a |> ¿f?

view this post on Zulip Anton (Aug 04 2026 at 11:52):

I was pretty happy with the previous -> syntax :p

view this post on Zulip Anton (Aug 04 2026 at 11:57):

We've also had these issues before: #bugs > too few args @ 💬

view this post on Zulip Andres Villegas (Aug 06 2026 at 04:53):

I am a bit late to the party, but if the main motivations is to make a = $b->func() where func returrn (type of a, type of b) assing to $b. wouldn't it be possible to apply the syntax sugar based on how it is used, for example a, $b = $b->func() will return normally, but a = $b->func() will assign one of the result to $b. I think that could make sense. I like the a = $b->func() pattern a lot and it comes up all the time


Last updated: Aug 12 2026 at 12:35 UTC