Stream: ideas

Topic: method for partial application


view this post on Zulip Eli Dowling (Mar 11 2024 at 23:00):

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.

view this post on Zulip Brendan Hansknecht (Mar 12 2024 at 01:27):

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.

view this post on Zulip Brendan Hansknecht (Mar 12 2024 at 01:30):

Also, do underscore have to be at the end. This opens question of interfaces like:

MyDest = ...

copyAndAppendHelloTo =
    copyAndAppend "hello" myDest _

copyAndAppendHelloTo =
    copyAndAppend "hello" _ myDest

view this post on Zulip Brendan Hansknecht (Mar 12 2024 at 01:30):

Which also has arg order problems to some extent

view this post on Zulip Brendan Hansknecht (Mar 12 2024 at 01:32):

And has confusion of something like:

superFunc = \a,b,c,d,e,...

partialSuperFunc =
    superFunc myA _ _ myD _ myE ...

view this post on Zulip Eli Dowling (Mar 12 2024 at 02:25):

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

view this post on Zulip Eli Dowling (Mar 12 2024 at 03:23):

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

view this post on Zulip Brendan Hansknecht (Mar 13 2024 at 03:53):

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.

view this post on Zulip Brendan Hansknecht (Mar 13 2024 at 03:54):

And yeah, I know verbosity of lambdas in pipes has been discussed before.


Last updated: Jun 16 2026 at 16:19 UTC