Stream: beginners

Topic: Why does `|>` pass the first argument?


view this post on Zulip J.Teeuwissen (Sep 10 2022 at 07:55):

I've been reading the FAQ about non default currying (https://github.com/roc-lang/roc/blob/main/FAQ.md#currying-and-the--operator) where is mentioned that

Of note, one possible design is to have currying while also having |> pass the last argument instead of the first. This is what Elm does, and it makes pipeline-friendliness and curry-friendliness the same thing. However, it also means that either |> Str.concat "!" would add the "!" to the front of the string, or else Str.concat's arguments would have to be flipped - meaning that Str.concat "Hello, World" "!" would evaluate to "!Hello, World".

And while I agree that Str.concat "Hello, World" "!" should evaluate to "Hello, World!", i'm not entirely sure as to why|>should not pass the last parameter. That would enable functions like map to keep the map func vals shape (which makes more sense in my opinion). Are there any downsides?

Conversely,

One reason the |> operator injects the value as the first argument is to make it work better with functions where argument order matters. For example, these two uses of List.append are equivalent:

List.append ["a", "b", "c"] "d"
["a", "b", "c"]
    |> List.append "d"

makes sense, but seems like an arbitrary choice when compared with

List.prepend "a" [ "b", "c", "d"]

where the following:

["b", "c", "d"]
    |> List.prepend "a"

wouldn't work as the list would be passed as the first parameter.

view this post on Zulip Georges Boris (Sep 10 2022 at 11:32):

This is the same pattern used on Elixir (and F#?) - so maybe it is the default expectation for non-curried FP langs?

I think the way Gleam does it is the best for this as it removes confusion while it becomes more powerful: https://gleam.run/book/tour/functions.html#function-capturing

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 13:01):

The Gleam example indeed seems nice (although it would add more syntax). And would work with either prepend/append by allowing functions to define the order of arguments in a way that makes sense for the function, while not having to list argument in such a way they work nicely with piping. Another option would be dropping piping as a whole and using multiline bodies instead. (which will give the opportunity for naming variables to improve readability). Come to think of it, what are benefits from piping over assigning it to variables?

view this post on Zulip jan kili (Sep 10 2022 at 13:16):

FWIW, I think List.prepend's parameters should switch places to support piping

view this post on Zulip jan kili (Sep 10 2022 at 13:18):

However, if we can make it so that in 80% of situations you want to pipe the first parameter (which is how I perceive it today), I think the remaining 20% are adequately supported by

["b", "c", "d"]
    |> x -> List.prepend "a" x

view this post on Zulip jan kili (Sep 10 2022 at 13:19):

7 extra characters to accommodate the nonstandard case :shrug:

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 13:35):

FWIW, I think List.prepend's parameters should switch places to support piping

That would make sense in a "pipe centric" design, but the (mentioned in tutorial) reason for piping using the first argument in the first place is exactly avoiding that scenario.

However, if we can make it so that in 80% of situations you want to pipe the first parameter (which is how I perceive it today), I think the remaining 20% are adequately supported by

How often is piping expected to be used? Ordering parameters would make sense if the bulk of the code would use piping. But if not, those same 7 extra characters would support piping (in the case of piping being the exception). Keeping the function types perhaps more sane.

view this post on Zulip jan kili (Sep 10 2022 at 13:55):

I personally try to design all of my Roc code around piping, but maybe I'm going overboard! :sweat_smile:

view this post on Zulip Georges Boris (Sep 10 2022 at 13:56):

Me as well :) (not only Roc but pretty much any functional language)

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 13:58):

I just found this related stream as well, for the reader: https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/Argument.20Ordering.20in.20Pipelines

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 14:05):

My experience in functional languages is limited to Haskell, where piping is not really a thing (or i'm missing out on something). The closest alternative is function composition (.) but this is more like (<|) in the sense that it reads from right to left. But AFAIK this won't be a feature in roc. That is perhaps the reason I feel weird about ordering arguments just to suit piping.

view this post on Zulip jan kili (Sep 10 2022 at 14:11):

An example of my love for piping: https://github.com/JanCVanB/advent-of-code-2021/blob/main/solutions/day-1-sonar-sweep/solution_1_part_1.roc

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:22):

I think the reason it targets the first parameter is that most function first pass in the main thing they are operating on and then pass in all of the parameters that define how to operate on that thing.

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:22):

Of course you could swap it, but i think going from most important to more extraneous is pretty natural

view this post on Zulip jan kili (Sep 10 2022 at 14:55):

Oh, also if piping was the last parameter then it would clash with the convention of making callback function(s) the last parameter(s). I'm not sure how important that is, but it seems tricky

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:57):

When using pipes, you can't use backpassing, so maybe that doesn't technically matter, but I guess that would limit you to either backpassing or pipes, not both.

view this post on Zulip Georges Boris (Sep 10 2022 at 15:02):

imo piping the last argument would only make sense if there was automatic currying... (having automatic currying would be pretty awesome for other reasons like applicatives but that is besides the point :sweat_smile:)

view this post on Zulip Richard Feldman (Sep 10 2022 at 15:53):

I've gotten feedback a few times that this section of the FAQ wasn't coming across well, so I took a pass at revising it...feedback welcome! https://github.com/roc-lang/roc/blob/5c8f91be887d6641d116d40133329b84a607e87d/FAQ.md#currying-and-the--operator

view this post on Zulip Richard Feldman (Sep 10 2022 at 15:56):

(the other sections are unchanged; I only edited the linked section, Currying and the |> operator)

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 16:31):

Regarding this bit: https://github.com/roc-lang/roc/blob/5c8f91be887d6641d116d40133329b84a607e87d/FAQ.md?plain=1#L362-L384
Wouldn't that work the other way around as well? if the numbers parameter would be a larger calculation than the function?

view this post on Zulip Brian Carroll (Sep 10 2022 at 17:32):

Good stuff Richard, I find this explanation more convincing than the previous one. :+1:

view this post on Zulip Brian Carroll (Sep 10 2022 at 17:33):

@J.Teeuwissen that's true in theory but in practice I find it's far more common for the function to be the larger part.

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 17:47):

that's true in theory but in practice I find it's far more common for the function to be the larger part.

Isn't the data part being the larger/(more processed) part the reason for pipelines to exist in the first place? In either case it will depend on the specific use case and both can be relieved with intermediate variables. But I guess you have to choose either one anyway.

view this post on Zulip Brian Carroll (Sep 10 2022 at 18:02):

When you compared the 'size' of function vs data I figured it meant the size of the code text. Not sure what other size metric would apply to both.

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 18:12):

Yes, that's what meant. With larger/(more processed) I meant that more processing requires more code. But in the case of piping this is not done inline of course.

view this post on Zulip Georges Boris (Sep 10 2022 at 18:50):

really like the new FAQ section as well :ok:

view this post on Zulip jan kili (Sep 10 2022 at 19:37):

Yeah the resulting data may in fact be more complex than any individual transformation function step, but that complexity is (or can be) spread across many piped steps. For example, if you wanted to declare some nested data monster, you could instead declare it as a simpler data structure piped through some nesting transformations.

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 19:38):

JanCVanB said:
The same can be said of functions, right? Especially in a functional language.

view this post on Zulip jan kili (Sep 10 2022 at 19:46):

I'm new to FP (Roc is my first PureFPL), but it seems to me that functions deconstructed into simplest lambda compinents will usually be more verbose per piece than data deconstructed into simplest numbers & arguments

view this post on Zulip jan kili (Sep 10 2022 at 19:46):

but at the end of the day maybe it's the same

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 19:52):

That's what first class citizenship will get you :rolling_on_the_floor_laughing:

view this post on Zulip Qqwy / Marten (Sep 12 2022 at 19:02):

J.Teeuwissen said:

My experience in functional languages is limited to Haskell, where piping is not really a thing (or i'm missing out on something). The closest alternative is function composition (.) but this is more like (<|) in the sense that it reads from right to left. But AFAIK this won't be a feature in roc. That is perhaps the reason I feel weird about ordering arguments just to suit piping.

In Haskell there is $ which behaves like <| and the somewhat newer & (in Data.Function) that behaves like |>.
(In these cases of course on the rightmost argument, or rather, on the 'single' argument that exists in a curried language.)
Of course, in Haskell it is also common to combine functions using . or <<< (right-to-left) or >>> (left-to-right; these last two are fromControl.Category/Control.Arrow) 'point-free' composition.

Ordering the most significant parameter first to suit piping (in Roc, F#, Elixir, etc.) is similar to ordering the most significant argument last to support a point-free style of programming (in Haskell, PureScript, etc.).


Last updated: Jul 06 2025 at 12:14 UTC