Stream: beginners

Topic: ✔ Task.loop must return an empty record in done state?


view this post on Zulip Ian McLerran (Sep 16 2024 at 16:28):

Hi all, was recently migrating a codebase to support the latest roc and basic-cli versions, and I was using a Task.loop call in my code.

I got a strange error message from my Task.loop usage:

Something is off with the body of this suffixed statement:
20      Task.loop! { client, previousMessages: initializeMessages } loop
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This loop call produces:
    Task {
        client : Client.Client,
        previousMessages : List Chat.Message,
    } []

But a suffixed statement is expected to resolve to an empty record:
    Task {} []

By changing my loop function to return an empty record when Done, I was able to resolve the issue. However, I see that the Task.loop function signature has not changed in the documentation. Is this a bug, or am I misunderstanding some changes to how Task.loop works now?

view this post on Zulip Ian McLerran (Sep 16 2024 at 16:45):

Update: apparently, this error message is caused because I was not capturing the return value from Task.loop. This seems like a problem, as one might want to write a loop function which sometimes you care about capturing its return value, and sometimes you don't. There should be an option to call Task.loop! without assigning its value, even if the function passed to Task.loop does include a non-empty record in its tagged Done {}a return.

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 18:07):

Yeah, lone ! statements are only allowed with fully ignorable values. That is {}.

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 18:08):

If the Done value wraps {}, it should work as a lone statement with !

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 18:26):

@Ian McLerran can I see the source?

view this post on Zulip Ian McLerran (Sep 16 2024 at 20:00):

Yeah, no problem. I'll grab it.

view this post on Zulip Ian McLerran (Sep 16 2024 at 20:37):

Full source here, and a snippet of the source here:

main =
    apiKey = getApiKey!
    model = getModelChoice!
    providerOrder = Dict.get preferredProviders model |> Result.withDefault []
    client = Chat.initClient { apiKey, model, providerOrder }
    Stdout.line! "Using model: $(model |> Ansi.color { fg: Standard Magenta })\n"
    Stdout.line! "Enter your questions below, or type 'Goodbye' to exit"
    Task.loop! { client, previousMessages: initializeMessages } loop # <--- error here ---
    Stdout.line (colorizeRole (Assistant "\nAssistant:  I have been a good chatbot. Goodbye! 😊"))

loop = \{ client, previousMessages } ->
    Stdout.write! "You: "
    query = Stdin.line!
    messages = Chat.appendUserMessage previousMessages query
    when query |> strToLower is
        "goodbye" -> Task.ok (Done { previousMessages }) # <--- Done {}
        "goodbye." -> Task.ok (Done { previousMessages }) # <--- Done {}
        "goodbye!" -> Task.ok (Done { previousMessages }) # <--- Done {}
        _ -> handlePrompt client messages

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 21:26):

And what is handlePrompt?

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 21:27):

But yeah, from this at first look, if those returned Done {}, I would expect that Task.loop! to function

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 21:28):

Cause that should lead to Task.loop returning Task {} err

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 21:28):

Might be worth adding a type loop to makes sure it is returning a Done {}.

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 21:29):

Obviously if you return Done { previousMessages }, it will require _ = Task.loop!, but with changing to Done {}, it looks all good assuming handlePrompt is all good as well.

view this post on Zulip Brendan Hansknecht (Sep 16 2024 at 21:29):

There is a small chance we implemented something wrong in the standard library that is messing up types here, but I doubt it.

view this post on Zulip Notification Bot (Sep 16 2024 at 21:36):

Ian McLerran has marked this topic as resolved.

view this post on Zulip Ian McLerran (Sep 16 2024 at 21:38):

Brendan Hansknecht said:

But yeah, from this at first look, if those returned Done {}, I would expect that Task.loop! to function

Oh yeah, sorry, replacing Done { previousMessages } with Done {} does work. I should have remembered I could just assign the result to a _ to resolve that error, and keep my function definition unchanged.


Last updated: Jul 06 2025 at 12:14 UTC