Hey everyone. I've been revisiting roc a bit in preparation for doing AoC, and comparing the experience with my previous attempts at doing AoC in Haskell. I came across a pattern that's bothering me a bit. For 2022 Day 1, the Haskell solution is
main = do
txt <- readFile "1.txt"
let elves = map (foldl (\x y -> x + (read y :: Int)) 0 . lines) . splitOn "\n\n" $ txt
print . maximum $ elves -- part 1
print . sum . take 3 . reverse . sort $ elves -- part 2
and in roc, just part one:
main =
input |> parse |> compute |> Stdout.line!
parse = \in -> in
|> Str.split "\n\n"
|> List.map \x -> x
|> Str.trim
|> Str.split "\n"
|> List.map \y -> y
|> Str.toU64
|> Result.withDefault 0
compute = \in -> in
|> List.map List.sum
|> List.max
|> Result.withDefault 0
|> Num.toStr
Now, being longer, more explicit, etc. is not necessarily bad, but what is annoying me a bit is the \in -> in |> function_chain
pattern, especially in the List.map
s. Is there a better way of doing this? Or is this just the trade-off for not having partial application? The reason it's bothering me (just a bit though) is because the function chaining / pipe is already partial application ... Why can't I have it for the first instance as well? Or, slightly different question, would it be possible to have some syntax sugar for starting a pipeline point-free style? Or should I just suck it up and accept \in -> in
?
How about simply
compute =
|> List.map List.sum
|> List.max
|> Result.withDefault 0
|> Num.toStr
and for where there is ambiguity
parse =
|> Str.split "\n\n"
|> List.map (
|> Str.trim
|> Str.split "\n"
|> List.map (
|> Str.toU64
|> Result.withDefault 0
)
)
or pehaps even
parse =
|> Str.split "\n\n"
|> List.map $
|> Str.trim
|> Str.split "\n"
|> List.map $
|> Str.toU64
|> Result.withDefault 0
Or should I just suck it up and accept
\in -> in
?
This has come up before, in general we like the current verbosity and simplicity trade-off.
There have been proposals for syntax sugar but they have not found wide appeal.
This proposal should cut down a lot on the general verbosity but not the lambda verbosity
Thanks for the links!
the static dispatch proposal would let you write those functions like this:
parse =
.split("\n\n")
.map(
.trim()
.split("\n")
.map(.toU64().withDefault(0))
)
compute =
.map(.sum())
.max()
.withDefault(0)
.toStr()
but personally I think I'd choose to put some lambdas in there just to give things some more names
I'll be honest and say I quite like the Haskell style calling with whitespace for the minimisation of parentheses cluttering the visual experience. But the snippet you posted is quite similar to what I came up with, if we remove (most of) the parentheses and replace .
with |>
. I mean, compute = .map(...)
is similar to compute = |> map (...)
.
(I also worry slightly that changing the syntax style specifically to be familiar could result in a regression to the mean --- it might be worth standing out. But that's just my two cents.)
Static dispatch includes the .map(.trim())
? I thought that was a potential extension and not generally accepted.
Personally, I really hate the syntax even if the idea is reasonable.
We done have partial application. Even if we did, calling a dot syntax function without a root just looks like a bug.
yeah sorry - I should have said that the proposal would let you write it like that
it's not essential and I'm not assuming it's a given
Is there already a yak-shaving thread for that part of the proposal? I don't want to initiate an undue flood of opinions
I don't think there is, but seems fine to start one :+1:
Yeah, shouldn't take over this thread with opinions on the static dispatch proposal...sorry
fwiw I've been finding it fascinating and it is actually related to my initial question. But I'm pretty much happy with how my code looks at the moment (what I was actually "annoyed" with was before I cleaned up the code a bit). But anyway, I appreciate everyone's input.
Last updated: Jul 06 2025 at 12:14 UTC