Stream: beginners

Topic: how to andMap without currying?


view this post on Zulip Georges Boris (Jul 14 2022 at 23:57):

hey folks - I was trying to implement something like andMap in Elixir and I was kinda stuck since there is no currying (the primitive typespec there won't help me tho I _can_ make it work just not in the type level atm).

I was wondering if there was already something available for Roc since it is a language with a proper typesystem but no currying as well.

view this post on Zulip Brian Carroll (Jul 15 2022 at 07:45):

I'm not 100% clear on what the exact problem is. Can you write out an example of how you wish it worked then we can suggest how to get it working?
The way to do currying in general is to return a function from a function. In other words we don't have _automatic_ currying but you can do it yourself. So that's the same as Elixir, I believe, isn't it?

view this post on Zulip Qqwy / Marten (Jul 15 2022 at 10:19):

What exactly do you want the semantics to andMap to be?
Something similar to Result.after perhaps?

Which function or behaviour from which Elixir library/module are you trying to port? I have quite a bit of experience with Elixir so I might be able to help with the difference and similarities between Elixir's and Roc's semantics.

view this post on Zulip Qqwy / Marten (Jul 15 2022 at 10:20):

:thinking: Wait. Do you want to port an Elixir snippet to Roc? Or a Roc snippet to Elixir?

view this post on Zulip Georges Boris (Jul 15 2022 at 10:45):

I'm actually trying to "port" something from Elm to Elixir and I think the trouble is similar because it is related to partial application.

but say I have something like:

a = Result a
b = Result b
c = Result c

and I want to create a function similar to mapN map3 myFn a b c

I can create various map map2 map3 etc but what is really useful is creating an andMap function, right?
I'm having trouble in defining such a function without relying on partial application.

view this post on Zulip Georges Boris (Jul 15 2022 at 11:36):

also... this is super useful for creating extensible type safe API's... not just mapping functors. so I'm curious on how to solve it without partial application.

e.g.
I've recently created an API for creating forms that look like this using the same pattern:

formFor User
|> string .name
|> int .age
|> select .role [ Admin, Editor, Viewer ]

view this post on Zulip Qqwy / Marten (Jul 15 2022 at 12:26):

Ah!

view this post on Zulip Qqwy / Marten (Jul 15 2022 at 12:26):

Yes, this is really difficult without automatic currying

view this post on Zulip Qqwy / Marten (Jul 15 2022 at 12:32):

I'm writing a Roc parser right now, and stumbled across the same thing.
Currently, the best design seems to be to manually curry the function:

a = Ok "John"
b = Ok "Doe"
c = Ok 42

Result (\a -> \b -> \c -> myFn a b c)
|> apply a
|> apply b
|> apply c

Where for Result, apply would look like:

apply : (Result (a -> b) *) x, Result a x-> Result b x
apply = \funResult, valResult ->
  when funResult is
    Err problem ->
      Err problem
    Ok fun ->
      when valResult is
        Err problem ->
          Err problem
        Ok val ->
          (fun val)

view this post on Zulip Qqwy / Marten (Jul 15 2022 at 12:36):

The Elixir Witchcraft library, for instance, curries functions you pass in to the analogous map/apply/const automatically for you (courtesy of a dynamic type system).

view this post on Zulip Qqwy / Marten (Jul 15 2022 at 12:37):

I wrote the Elixir library Currying a while back to do (only) currying for you when desired.

view this post on Zulip Qqwy / Marten (Jul 15 2022 at 12:38):

If you want to write your own thing.

view this post on Zulip Qqwy / Marten (Jul 15 2022 at 12:41):

As for Roc: What we _do_ have builtin right now is the backpassing syntax and e.g. Result.after (and Task.after, List.joinMap etc) which work with it.
This is fine for types for which there is no difference in how they work 'monadically' vs 'applicatively'. (Which is not all of them; applicative implementations can be more efficient and more flexible.)

But for those you can write e.g.

aVal <- Result.after a
bVal <- Result.after b
cVal <- Result.after c
myFun aVal bVal cVal

in Roc

view this post on Zulip Georges Boris (Jul 15 2022 at 13:02):

Yeah – in Roc the manually curried function is not that bad… in Elixir is kinda crazy how verbose the lambdas are.

But we do have the Kernel.apply that can use a list of values to a given function arguments and call it.
I've found this article that talks about "Tuplicative" and I wonder if it could be of any value to this discussion…

I wonder what would the API for something like Json.Decode.Pipeline look like in Roc.

view this post on Zulip Richard Feldman (Jul 15 2022 at 13:06):

my thinking was always to do it with backpassing, e.g. porting the example from the docs

userDecoder : Decoder User
userDecoder =
    id <- required "id" int
    email <- required "email" (nullable string)
    name <- optional "name" string "(fallback if name is `null` or not present)"

    Decode.succeed { id, email, name, percentExcited: 1.0 }

view this post on Zulip Richard Feldman (Jul 15 2022 at 13:08):

of course the types of required and optional would be different in that design

view this post on Zulip Georges Boris (Jul 15 2022 at 15:55):

Just doing a mock design here - but maybe this could also work for my form example:

string : Form (a -> String) (Form a) -> Form a
int : Form (a -> Int) (Form a) -> Form a
select : Form (a -> option) (List option) -> Form a

form =
    fields <- string .name Fields.empty -- a : { a & name : String }
    fields1 <- int .age fields -- a : { a & name: String, age: Int }
    fields2 <- select .role [ Admin, Editor, Viewer ] fields1 -- a : { a & name: String, age: Int, role: [ Admin, Editor, Viewer ]  }
    formFor { name: "name", age: 20, role: Admin } fields2

Would that work as expected? With extensible records I think(?) order would be irrelevant tho the backpassing would make passing the previous values explicitly a lil bit verbose. Not a deal breaker. (does Roc support shadowing?) if so the 1 2 3 could be simplified a bit.


Last updated: Jul 06 2025 at 12:14 UTC