Maybe I'm one of the very few that like the operators in Haskell. Maybe it's because I haven't had to write or read any production Haskell. But I kinda miss more expressive operators. For example, in Roc, I find myself doing:
getList input |> List.map func1 |> List.map func2
or
tryThis input |> Result.map func1 |> Result.map func2
and this can get pretty verbose. I wish we had a "map-pipe" operator, for example &>, which would apply the function to values inside the variable. So it would look like this, instead:
getList input &> func1 &> func2
Now, it would be best if it would work for all functors, but introducing this term might be confusing and scary. But I like operating on more abstract levels, i.e., "just do this to stuff inside". And having Result.map all over the code makes it less enjoyable to work with Results.
If we had pointfree composition, this could be solved instead by doing:
getList input |> Result.map (func2 . func1)
but we don't, for other reasons. And maybe we shouldn't have &> for the same reasons, but we already have |>, so why not one more?
What are your opinions?
I personally prefer the more verbose version because it is more explicit and really easy to follow. I can see immediately that this is a Result and not some other thing. Is the primary concern writing or reading this? Does it help to split onto separate lines? I guess being verbose the issue is that it spreads everything out which makes it harder to read all in one screen at once?
The general design philosophy is to keep the language as simple and small as possible. So where Haskell or C++ ask "why not one more?", Roc tends to ask "do we really need it?" :joy:
Luke Boswell said:
I personally prefer the more verbose version because it is more explicit and really easy to follow. I can see immediately that this is a Result and not some other thing. Is the primary concern writing or reading this? Does it help to split onto separate lines? I guess being verbose the issue is that it spreads everything out which makes it harder to read all in one screen at once?
Being explicit is a fair point, but I don't think that we should rely on the map function to convey type information. Mapping is just mapping and what the outer type is should be inferred from context (e.g., the pipeline ends in Result.withDefault).
Separate lines kinda help, but then I feel like we waste a lot of screen space. I've also noticed that my expressions/pipelines usually just about fit on one line, although less readable line, balancing in the grey area between "short enough for one line" and "long enough for separate lines".
Writing is also a bit more painful, but that might be eased with a proper language server.
Brian Carroll said:
The general design philosophy is to keep the language as simple and small as possible. So where Haskell or C++ ask "why not one more?", Roc tends to ask "do we really need it?" :joy:
I know that Roc is pretty strict about such ideas, that's why I'm not afraid of proposing this and it being implemented without good reasons ;]
personally, my issue with the function shown above is the potential perf cost.
which do you want? They can have wildly different performance charcateristics in some cases:
getList input |> List.map func1 |> List.map func2
getList input |> List.map \x -> x |> func1 |> func2
In haskell (laziness) and rust (iterators) you tend to get the second one. In roc, you have to explicitly pick which one you want (at least currently)
Not having laziness or iterators is also something I miss :sweat_smile:
I think second one is preferable in most cases, but writing that in Roc is even more verbose/uncomfortable (because of the lack of currying).
And I know those are not critical issues, but it's a part of developer experience and developer experience is important
Is there something I can read which explains the general reasoning behind this distinction? Is it not possoble for the compiler to see they are doing the same thing?
We could add a compiler optimization for this in the future
That said, you don't always want the second version. really depends on a number of things. But I do think it is the better default
In what scenario would you prefer first version instead of the second one?
We definitely care about the developer experience. We don't have currying because of the negative consequences to the beginner developer experience.
I didn't mean to imply that you don't!
Not having currying also have negative consequences. Everything has its upsides and downsides, so deciding where to draw the line is definitely tricky. And you can't appeal to everyone
I think in general we prefer to keep things simple even when it makes things somewhat more verbose.
In what scenario would you prefer first version instead of the second one?
Performance wise, it depends on the internal calculations and the output type sizes. If the input and output types are the same size, we can do List.map in place. On top of that, many small hotter loops can often be faster than if you make the loop body too big. So I have seen similar things in hot loops parsing bytes for example (though that would I guess be two List.walk in a row rather than two List.map in a row, but it is the same concept fundamentally.).
Anton said:
I think in general we prefer to keep things simple even when it makes things somewhat more verbose.
Fair. I may not agree on some decision, but I definitely still believe in The Vision™!
Last updated: Jun 16 2026 at 16:19 UTC