Stream: beginners

Topic: Redundant function arguments vs. partial application


view this post on Zulip Paul Joubert (Nov 28 2024 at 14:35):

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.maps. 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?

view this post on Zulip Paul Joubert (Nov 28 2024 at 14:56):

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

view this post on Zulip Anton (Nov 28 2024 at 15:02):

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

view this post on Zulip Paul Joubert (Nov 28 2024 at 15:04):

Thanks for the links!

view this post on Zulip Richard Feldman (Nov 28 2024 at 15:15):

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()

view this post on Zulip Richard Feldman (Nov 28 2024 at 15:16):

but personally I think I'd choose to put some lambdas in there just to give things some more names

view this post on Zulip Paul Joubert (Nov 28 2024 at 15:32):

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.)

view this post on Zulip Brendan Hansknecht (Nov 28 2024 at 16:45):

Static dispatch includes the .map(.trim())? I thought that was a potential extension and not generally accepted.

view this post on Zulip Brendan Hansknecht (Nov 28 2024 at 16:46):

Personally, I really hate the syntax even if the idea is reasonable.

view this post on Zulip Brendan Hansknecht (Nov 28 2024 at 16:47):

We done have partial application. Even if we did, calling a dot syntax function without a root just looks like a bug.

view this post on Zulip Richard Feldman (Nov 28 2024 at 17:24):

yeah sorry - I should have said that the proposal would let you write it like that

view this post on Zulip Richard Feldman (Nov 28 2024 at 17:25):

it's not essential and I'm not assuming it's a given

view this post on Zulip Sam Mohr (Nov 28 2024 at 17:27):

Is there already a yak-shaving thread for that part of the proposal? I don't want to initiate an undue flood of opinions

view this post on Zulip Richard Feldman (Nov 28 2024 at 17:40):

I don't think there is, but seems fine to start one :+1:

view this post on Zulip Brendan Hansknecht (Nov 28 2024 at 17:47):

Yeah, shouldn't take over this thread with opinions on the static dispatch proposal...sorry

view this post on Zulip Paul Joubert (Nov 28 2024 at 18:24):

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