Stream: ideas

Topic: Do we still need `|>`?


view this post on Zulip jan kili (Feb 12 2025 at 21:47):

I think so! At least until method-style function calls land in Roc, but possibly even after that. I've seen it mentioned several times that |> (also known as the pipe operator, or jokingly pizza :pizza:) can/should be removed from Roc with WSA, but I'm skeptical of that. Since it's one of Roc's most recognizable operators (and personally one of my favorite elements of its syntax), I'd like us to fully reconsider it (and its family of related operators like .) in the context of the syntax changes coming this year.

I think pipe shines best in multi-line pipelines. Here's an example I've used in previous topics...

main! = |_|
    "./input.txt"
    |> Path.from_str
    |> try Path.read_bytes!
    |> try Foo.from_bytes
    |> transform(2, Much)
    |> try Foo.to_bytes
    |> Path.write_bytes!(Path.from_str("./output.txt"))?

    Stdout.line!("🥳 See ./output.txt")

to ask about how we expect multi-line function call chains might look when method-style function calls land with static dispatch in several months after the compiler rewrite:

main! = |_|
    "./input.txt"
    .(Path.from_str)
    .read_bytes!?
    .(Foo.from_bytes)?
    .(transform)(2, Much)
    .to_bytes?
    .(Path.write_bytes!)(Path.from_str("./output.txt"))

    Stdout.line!("🥳 See ./output.txt")

It seems that .(local_fn) (also known as the "pass to" syntax) could be a one-to-one replacement for most pipes. However, I'm unclear whether folks want .(local_fn) to be that ubiquitous, and suspect that there are many circumstances where having a non-method-style function chaining syntax is desirable.

Does anyone have an example of PNC+SD code that feels better as PNC+SD+:pizza:?

view this post on Zulip Sam Mohr (Feb 12 2025 at 22:00):

The main problem is precedence, how do you make |> work with method calls in a way that doesn't look weird?

data = start.call() |> local_func.next_method()

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

You'd have to make a space-based operator bind the same as dots

view this post on Zulip Sam Mohr (Feb 12 2025 at 22:02):

Also, the current plan is for .method().other() to act as sugar for |x| x.method().other() (from an old example)

view this post on Zulip Sam Mohr (Feb 12 2025 at 22:03):

I will agree that from an Elm/FP-in-general background, I much prefer the reading of |> over the .(local_func)(args) thing

view this post on Zulip Sam Mohr (Feb 12 2025 at 22:05):

With the recent whitespace discussions, we could make |> always add newlines to the whole chain, turning the above to

data =
    start.call()
    |> local_func
    .next_method()

view this post on Zulip Sam Mohr (Feb 12 2025 at 22:10):

Which I believe is less confusing

view this post on Zulip Richard Feldman (Feb 12 2025 at 22:36):

I realize I may be in the minority on this, but I really like being able to do:

time = 4.(hours).(ago!)

and I like that a lot better than

time = 4 |> hours |> ago!

view this post on Zulip Richard Feldman (Feb 12 2025 at 22:37):

I'm not a big fan of .(fn)(arg) but I don't have a great alternative idea either

view this post on Zulip Richard Feldman (Feb 12 2025 at 22:38):

I do like . being the consistent chaining/autocomplete character, and I do like the precedence being more straightforward and easy to understand

view this post on Zulip Sam Mohr (Feb 12 2025 at 22:41):

If it weren't even more parens, I'd like it more

view this post on Zulip Sam Mohr (Feb 12 2025 at 22:42):

But the alternatives we discussed weren't great either

view this post on Zulip Sam Mohr (Feb 12 2025 at 22:42):

I'm considering throughout these discussions that most function chains should be methods anyway

view this post on Zulip Sam Mohr (Feb 12 2025 at 22:43):

So all these examples with |> or .(func) are good at showing how these would look when used in volume

view this post on Zulip Sam Mohr (Feb 12 2025 at 22:43):

But they inflate the perceived frequency of these tools

view this post on Zulip Richard Feldman (Feb 12 2025 at 22:43):

to be fair though, I'm also not a fan of multi-arg PNC with pipe:

|> fn(arg1, arg2)

that super doesn't look like a function that is being passed 3 arguments :sweat_smile:

view this post on Zulip Richard Feldman (Feb 12 2025 at 22:43):

and yet it is

view this post on Zulip Sam Mohr (Feb 12 2025 at 22:44):

I'd say val.method(arg1, arg2) is the same thing, we're just used to it

view this post on Zulip Sam Mohr (Feb 12 2025 at 22:44):

But we're used to it so...

view this post on Zulip Richard Feldman (Feb 12 2025 at 22:46):

fair!

view this post on Zulip Richard Feldman (Feb 12 2025 at 22:47):

but yes, I am used to it and it doesn't look weird to me :laughing:

view this post on Zulip Brendan Hansknecht (Feb 13 2025 at 00:07):

Richard Feldman said:

I realize I may be in the minority on this, but I really like being able to do:

time = 4.(hours).(ago!)

and I like that a lot better than

time = 4 |> hours |> ago!

I totally agree. I really like the .() syntax in general for local calls

view this post on Zulip Sky Rose (Feb 13 2025 at 04:33):

I'm not a big fan of .(fn)(arg) but I don't have a great alternative idea either

I remember .$ and .. came up as options a couple times. arg1..fn(arg2). Were they ever ruled out?

view this post on Zulip jan kili (Feb 13 2025 at 04:35):

.$ is getting some re-evaluation in #ideas > static dispatch - pass_to alternative , along with discussion of all alternatives for the method-style syntax version of |>

view this post on Zulip jan kili (Feb 18 2025 at 19:02):

This seems like consensus to fully remove |> by not implementing it in the new compiler.

view this post on Zulip jan kili (Feb 18 2025 at 19:04):

For deprecation, I propose matching whatever is decided in #contributing > deprecating WSA (whitespace application for function calls) but without any pre-v0.1.0 PRs because the replacement syntax won't work before then.


Last updated: Jun 16 2026 at 16:19 UTC