Stream: beginners

Topic: Task apply


view this post on Zulip Luke Boswell (Jun 10 2023 at 00:29):

I've been looking at the new record builder syntax, and not sure if it is possible to use it currently. I think we need a new function added to basic-cli to enable the applicative API, but not sure. Am I correct in saying that we need the following function to be able to use record builders with Task?

Task.apply : Task a err -> Task (a -> b) err -> Task b err

My goal was to understand what an applicative API looks like, so I can understand where this language feature may be applied.

view this post on Zulip Luke Boswell (Jun 10 2023 at 00:46):

I've made an example below, it's contrived but I wanted to just get something basic working.

% roc run Experiments/Applicative.roc
Apples: Granny Smith, Pink Lady, Golden Delicious
Oranges: Navel, Blood Orange, Clementine
app "applicative"
    packages {
        pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3.2/tE4xS_zLdmmxmHwHih9kHWQ7fsXtJr7W7h3425-eZFk.tar.br",
    }
    imports [
        pf.Stdout,
        pf.Task.{Task},
    ]
    provides [main] to pf

main =

    myrecord : Task { apples : List Str, oranges : List Str } []
    myrecord = Task.succeed {
        apples: <- getFruit Apples |> apply,
        oranges: <- getFruit Oranges |> apply,
    }

    {apples,oranges} <- myrecord |> Task.await

    "Apples: "
    |> Str.concat (Str.joinWith apples ", ")
    |> Str.concat "\n"
    |> Str.concat "Oranges: "
    |> Str.concat (Str.joinWith oranges ", ")
    |> Stdout.line

getFruit : [Apples, Oranges] -> Task (List Str) []
getFruit = \request ->
    when request is
        Apples -> Task.succeed ["Granny Smith", "Pink Lady", "Golden Delicious"]
        Oranges -> Task.succeed ["Navel", "Blood Orange", "Clementine"]

apply : Task a err -> (Task (a -> b) err -> Task b err)
apply = \first -> \second ->
    result <- second |> Task.attempt

    when result is
        Ok f -> Task.map first f
        Err err -> Task.fail err

view this post on Zulip Luke Boswell (Jun 10 2023 at 01:12):

Ok, I think this is really cool. It also works with Result :smiley:

% roc run Experiments/Applicative.roc
Insufficient fruit available
app "applicative"
    packages {
        pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3.2/tE4xS_zLdmmxmHwHih9kHWQ7fsXtJr7W7h3425-eZFk.tar.br",
    }
    imports [
        pf.Stdout,
    ]
    provides [main] to pf

main =

    myrecord : Result { apples : List Str, oranges : List Str } [LackOfFruit]
    myrecord = Ok {
        apples: <- getFruit Apples |> apply,
        oranges: <- getFruit Oranges |> apply,
    }

    when myrecord is
        Ok {apples,oranges} ->
            "Apples: "
            |> Str.concat (Str.joinWith apples ", ")
            |> Str.concat "\n"
            |> Str.concat "Oranges: "
            |> Str.concat (Str.joinWith oranges ", ")
            |> Stdout.line
        Err LackOfFruit ->
            Stdout.line "Insufficient fruit available"

getFruit : [Apples, Oranges] -> Result (List Str) [LackOfFruit]
getFruit = \request ->
    when request is
        Apples -> Ok ["Granny Smith", "Pink Lady", "Golden Delicious"]
        # Oranges -> Ok ["Navel", "Blood Orange", "Clementine"]
        Oranges -> Err LackOfFruit

apply : Result a err -> (Result (a -> b) err -> Result b err)
apply = \first -> \second ->
    when second is
        Ok f -> Result.map first f
        Err err -> Err err

view this post on Zulip Luke Boswell (Jun 10 2023 at 01:14):

So, I guess my question here is probably more suited for #ideas but, should we add the applicative "apply" to Result and Task to enable the use of record builder?

view this post on Zulip Luke Boswell (Jun 10 2023 at 01:14):

and a follow up question, what should the implementation look like, I just used first and second as I have no idea what to name these variables.

view this post on Zulip Richard Feldman (Jun 10 2023 at 01:26):

Luke Boswell said:

So, I guess my question here is probably more suited for #ideas but, should we add the applicative "apply" to Result and Task to enable the use of record builder?

I think it's a good idea to should start an #ideas for Result :thumbs_up:

view this post on Zulip Richard Feldman (Jun 10 2023 at 01:28):

for Task, we 100% definitely want to, and I like the name Task.batch for it. If others prefer a different name, we could discuss, but I'm also fine with saying we could go ahead with adding Task.batch to basic-cli and implement it by chaining together Task.awaits

view this post on Zulip Richard Feldman (Jun 10 2023 at 01:28):

having Task.batch run concurrently requires #ideas > Task as builtin (apply and map2 are two sides of the same coin, and each can be implemented in terms of the other) but we can still offer the API right now even if it doesn't run concurrently, and then upgrade it behind the scenes in the future to make it concurrent!

view this post on Zulip Richard Feldman (Jun 10 2023 at 01:29):

also I think we might as well add map2 for both Result and Task while we're at it :smiley:

view this post on Zulip Richard Feldman (Jun 10 2023 at 01:32):

Luke Boswell said:

myrecord : Result { apples : List Str, oranges : List Str } [LackOfFruit]
myrecord = Ok {
    apples: <- getFruit Apples |> apply,
    oranges: <- getFruit Oranges |> apply,
}

Interesting - I never even considered this usage!

When you're building a record anyway, it's a more concise alternative to Result.try and backpassing:

myrecord : Result { apples : List Str, oranges : List Str } [LackOfFruit]
myrecord =
    apples <- getFruit Apples |> Result.try,
    oranges <- getFruit Oranges |> Result.try,

    Ok { apples, oranges }

view this post on Zulip Luke Boswell (Jun 10 2023 at 02:07):

@Richard Feldman do the function arguments to batch have special names? I am using first and second here which seem wrong

view this post on Zulip Richard Feldman (Jun 10 2023 at 02:08):

that's a good question! I'm not aware of any precedent for good names here, to be honest :sweat_smile:

view this post on Zulip Richard Feldman (Jun 10 2023 at 02:09):

it's a type that is all of these things:

view this post on Zulip Richard Feldman (Jun 10 2023 at 02:09):

hopefully we can figure some out!

view this post on Zulip Luke Boswell (Jun 10 2023 at 02:13):

Posting in #ideas


Last updated: Jul 05 2025 at 12:14 UTC