I understand roc has no macros (even though it seems to be fully expression-based), so I am inclined to guess 'no'.
no, could not be implemented in userspace
would have to be a compiler builtin
thanks :thumbs_up:
what is the rationale for having one, but not the other? i.e. what makes pipe-first special?
Mostly around api design. Roc is not a curried language (curried languages tend to promote pipe last apis). As such, roc tends to design apis such that the main element is the first element of a function. List.someThing functions always take the List first. Str.someThing the Str first. So only having pipe first helps to keep things consistent and promote a specific style
There has never really been a need or request for pipe last.
Though there have definitely been requests for a generic place holder based piping. That said, so far a lambda has been the preferred only supported to do that.
Something like list |> someFunc 123 _ "test"
where _
is where the pipe goes to.
OK, fair enough...let me just make sure I understand you very first point about currying, because I'm not even sure where it comes into play. If I have a chain of 3 functions, and they happen to be able to be pipe-able at the last position, what does that have to do with currying?
For what its worth, in Clojure we have ->
, ->>
, as->
, the latter being the most generic one, but the one used by far the least.
in other words, thread-first
& thread-last
are the easiest to implement (don't need to introduce any placeholder symbols), and cover 99% of the use-cases.
It is just that in curried languages it tends to be more common to put the thing operated on as the last argument. This is so that you can partially apply what the function does leaving only what is operated on.
So a find function in a curried language is likely to be find needle haystack
. When partially applied you would do findCoffee = find "coffee"
. When used in a pipe, you would want to pipe last because of this design
in a curried language, |> find "coffee"
. That needs pipe last to work correctly.
Given roc isn't curried, we have chosen the convention to put what is operated on first. This is essentially an arbitrary convention.
A larger writeup: https://www.roc-lang.org/faq#curried-pipes
in other words,
thread-first
&thread-last
are the easiest to implement (don't need to introduce any placeholder symbols), and cover 99% of the use-cases.
I think thread-first
and general language api conventions covers about 98% of the use cases.
If that turns out to not be true, when friction arises, we can add thread-last
hmm ok i think i get it...there is no way to make up a function from another function (e.g. composition, currying etc) at runtime, right?
Yeah, you would have to use an explicit lambda \x -> Num.add x 7
indeed, with this restriction in mind 98% sounds about right :)
It does feel terrible if you have a pipe and have to do something like:
|> \buffer -> Str.concat "prefix: " buffer
but if I can create a lambda, then in theory there could be a compose
, or a partial
builtin, right?
I guess you could make a helper function and do:
|> (flip Str.concat) "prefix: "
or a plain function that returns a lambda?
i see
yeah, you can also manually curry in roc.
functions that return functions
Just a bit less ergonomic cause you have to add extra parens when calling compared to a curried language
i do not mind parens :)
many thanks for your explanation :thumbs_up:
Glad to help
Given roc isn't curried, we have chosen the convention to put what is operated on first. This is essentially an arbitrary convention.
One big advantage of this convention is that it allows the trailing lambda syntax to work nicely because the thing being operated on is not the last argument
Last updated: Jul 06 2025 at 12:14 UTC