Stream: beginners

Topic: Tasks and Error handling


view this post on Zulip walther (Nov 28 2023 at 18:55):

Hi, new here and trying to figure out some error handling.

My advent of code for 2022 day 01:

app "day01"
    packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
    imports [pf.Stdout, pf.Stderr, pf.Task, pf.File, pf.Path]
    provides [main] to pf

inputPath = "day01.txt"

main =
    input <- File.readUtf8 (Path.fromStr "input/\(inputPath)") |> Task.attempt
    when input is
        Ok in -> when part01 in is
            Ok out -> Num.toStr out |> Stdout.line
            Err e -> when e is
                InvalidNumStr -> Stderr.line "Error running part01 for \(inputPath), invalid num str"
                ListWasEmpty -> Stderr.line "Error running part01 for \(inputPath), ListWasEmpty"
        Err _e -> Stderr.line "Error running part01 for \(inputPath), no file"

part01 : Str -> Result U64 [InvalidNumStr, ListWasEmpty]
part01 = \input ->
    parsed <- Str.trim input
        |> Str.split "\n\n"
        |> List.mapTry (\xs -> Str.split xs "\n" |> List.mapTry Str.toU64)
        |> Result.try
    List.map parsed List.sum |> List.max

Questions:

view this post on Zulip Luke Boswell (Nov 28 2023 at 18:57):

Check out https://www.roc-lang.org/examples/Tasks/README.html. It's only just been updated to hopefully assist with this. Would love to know if it helps you.

view this post on Zulip Luke Boswell (Nov 28 2023 at 19:11):

Maybe we should rename this now to "Tasks and Error Handling" now it has a slightly bigger emphasis on errors and might make it easier to find?

view this post on Zulip Luke Boswell (Nov 28 2023 at 19:26):

We could also maybe link to this example in the Tutorial, at the end of the tasks section too.

view this post on Zulip walther (Nov 28 2023 at 21:46):

Thanks for the link. I'd actually skimmed the PR before, but took a closer look now.

It looks like Task.await is one of the things I'm looking for. It has the same feeling of "bubbling up" as Result.try. Instead of using Task.attempt and pattern matching immediately.

One of the things I was missing, and which I don't think is in the Tasks example, is Task.fromResult. I'm guessing this should be pretty commonly used? Or perhaps there's a better way to compose Result and Task. I updated my code, lmk if there's still something to be improved.

main =
    run |> Task.onErr handleErr

run : Task.Task {} [FileReadErr, InvalidNumStr, ListWasEmpty]
run =
    input <- File.readUtf8 (Path.fromStr "input/\(inputPath)")
        |> Task.mapErr \_ -> FileReadErr
        |> Task.await
    outPart01 <- part01 input |> Task.fromResult |> Task.await
    Num.toStr outPart01 |> Stdout.line

handleErr = \err ->
    when err is
        FileReadErr -> Stderr.line "Error running part01 for \(inputPath), file read err"
        InvalidNumStr -> Stderr.line "Error running part01 for \(inputPath), invalid num str"
        ListWasEmpty -> Stderr.line "Error running part01 for \(inputPath), ListWasEmpty"

view this post on Zulip Luke Boswell (Nov 28 2023 at 22:02):

I had a play with your code to explore the Task.fromResult a bit more. I think I would do it like this (not that this is the correct way or anything, just how I like it).

app "hello-world"
    packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.2/c7T4Hp8bAdWz3r9ZrhboBzibCjJag8d0IP_ljb42yVc.tar.br" }
    imports [
        pf.Stdout,
        pf.Stderr,
        pf.File,
        pf.Path,
        pf.Task.{ Task },
    ]
    provides [main] to pf

inputPath = "day01.txt"

run : Task {} [FileReadErr, InvalidNumStr, ListWasEmpty]
run =
    outPart01 <-
        Path.fromStr "input/\(inputPath)"
        |> File.readUtf8
        |> Task.mapErr \_ -> FileReadErr
        |> Task.await \input -> input |> part01 |> Task.fromResult
        |> Task.await

    Stdout.line "\(Num.toStr outPart01)"

part01 : Str -> Result U64 [InvalidNumStr, ListWasEmpty]
part01 = \input ->
    parsed <-
        Str.trim input
        |> Str.split "\n\n"
        |> List.mapTry (\xs -> Str.split xs "\n" |> List.mapTry Str.toU64)
        |> Result.try

    List.map parsed List.sum |> List.max

main = run |> Task.onErr handleErr

handleErr = \err ->
    when err is
        FileReadErr -> Stderr.line "Error running part01 for \(inputPath), file read err"
        InvalidNumStr -> Stderr.line "Error running part01 for \(inputPath), invalid num str"
        ListWasEmpty -> Stderr.line "Error running part01 for \(inputPath), ListWasEmpty"

view this post on Zulip Luke Boswell (Nov 28 2023 at 22:07):

I haven't used Task.fromResult much, it's looks quite useful. Thank you

view this post on Zulip walther (Nov 29 2023 at 14:54):

Thanks for your example! It's very helpful to see how others would approach this


Last updated: Jul 05 2025 at 12:14 UTC