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.
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?
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.
:thinking: Wait. Do you want to port an Elixir snippet to Roc? Or a Roc snippet to Elixir?
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.
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 ]
Ah!
Yes, this is really difficult without automatic currying
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)
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).
I wrote the Elixir library Currying a while back to do (only) currying for you when desired.
If you want to write your own thing.
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
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.
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 }
of course the types of required
and optional
would be different in that design
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