One of the few design decisions in roc that makes me annoyed is the lack of partial application/currying.
I actually agree with most of roc's reasons for not including it, but the added friction when making specialized versions of more generic functions does annoy me.
It's something I do a lot when writing F# and ocaml code and I find it is very much a "pit of success" type effect. It makes writing good code the easiest path. It encourages creating functions that are very generic and easy to test and then easily making specialized versions of them either in place where the same inputs are going to be used more than once, or when defining it to create versions with sensible defaults.
I recently saw the gleam language, It allows using an underscore in a function call to automatically return a partially applied version of that function. I think it mitigates your concerns with currying ( weird errors) and provides some advantages.
To show this in practice I've roughly translated some code i recently wrote in the roc language server(rust obviously also has this limitation)
getCompletionItems = \registry, url, position ->
[{ label: "completion", docs: Some "this is a completion", lots: 1, of: Invalid, other: "", stuff: None }] |> Task.ok
unwrapCompletionResponse = \completions ->
completions |> List.map \comp -> { label: comp.label, docs: comp.docs }
getCompletionStrings = \transformer, reg, url, position ->
items <- reg |> getCompletionItems url position |> Task.map
items
|> unwrapCompletionResponse
|> transformer
getCompletionLabels = \reg, url, position ->
getCompletionStrings (\c -> List.map c .label) reg url position
getCompletionDocs = \reg, url, position ->
getCompletionStrings
(\c ->
c
|> List.keepOks \comp ->
when comp.docs is
Some docStr -> Ok docStr
_ -> Err None)
reg
url
position
Now with the proposed underscore
getCompletionLabels = getCompletionStrings (List.map _ .label) _ _ _
getCompletionDocs =
trans=List.keepOks _ \comp ->
when comp.docs is
Some docStr -> Ok docStr
_ -> Err None))
getCompletionStrings trans _ _ _
I think when reading this code it is less cluttered with variables that don't matter and is more clear what is actually being done.
It also eliminates a whole collection of errors that can occur when you accidentally switch the order of variables(something i have done, and i assure you, it's a terrible bug to track down)
Eg:
copyAndAppend=\append, source ,dest-> #does something
copyAndAppendHello \source, dest-> copyAndAppend "hello" dest source
Errors of that sort tend to be very difficult to identify in a large codebase with many interlocking pieces, often in my experience it just results in subtly wrong behavior.
Hmm, but the second example doesn't look like a function anymore. It looks like a value. I like that clean separation in roc. I do occasionally miss currying, but I honestly have never been that heavy of a user of it, so may not know the depth of what I am actually missing.
Also, do underscore have to be at the end. This opens question of interfaces like:
MyDest = ...
copyAndAppendHelloTo =
copyAndAppend "hello" myDest _
copyAndAppendHelloTo =
copyAndAppend "hello" _ myDest
Which also has arg order problems to some extent
And has confusion of something like:
superFunc = \a,b,c,d,e,...
partialSuperFunc =
superFunc myA _ _ myD _ myE ...
I mean, you can make a record with a function as a value, and functions can return functions.
In an IDE hover would tell you the type of those values, and highlighting would indicate that they are functions.
I actually didn't think of the ordering at all. I'm so used to designing my functions to make use of partial application I just order params by "specificity".
I'd say in roc's case it shouldn't have to be at the end, because though it can add to confusion it's more versatile.
If you did want to limit it I'd actually suggest that the _ should have to be for the first arguments
I think that because in curried/partially applied languages you'd expect a function to have this signature:
upperCase: Language, Str->Str
#patially applied
upperCaseEng= upperCase English
This makes sense because you might want to partially apply the language you will be doing casing for to make "upperCaseEnglish", or with a pipe you'd run this code: "hi"|>uppercase English.
Roc's pipe operator puts the piped var at the start though, so you would order your args the other way around.
upperCase: Str,Language->Str
#patially applied
upperCaseEng= upperCase _ English
Another good example
message |> Str.fromUtf8 |> Result.try (\s -> Str.splitFirst s "\r\n\r\n") |> Task.fromResult |> Task.await
# becomes:
message |> Str.fromUtf8 |> Result.try (Str.splitFirst _ "\r\n\r\n") |> Task.fromResult |> Task.await
I mean, you can make a record with a function as a value, and functions can return functions.
Very true, but a lot rarer than this would likely be.
And yeah, I know verbosity of lambdas in pipes has been discussed before.
Last updated: Jun 16 2026 at 16:19 UTC