Looping Tasks

Sometimes, you need to repeat a task, or a chain of tasks, multiple times until a particular event occurs. In roc, you can use Task.loop to do this.

We'll demonstrate this by adding numbers read from stdin until the end of input (Ctrl-D or end of file).

Task.loop starts from an initial state and sends it to a provided function that will return a new Task.

Code step by step

Task.loop 0 addNumberFromStdin

We provide loop with:

This function takes our current sum total, reads a line from stdin and returns one of the following:

Take a moment to match this behavior with the type signature of addNumberFromStdin:

I64 -> Task [Done I64, Step I64] [NotNum Str]

This is where the action happens:

addNumberFromStdin = \sum ->
    input = Stdin.line!

    addResult =
        when input is
            Input text ->
                when Str.toI64 text is
                    Ok num ->
                        Ok (Step (sum + num))

                    Err InvalidNumStr ->
                        Err (NotNum text)

            End -> Ok (Done sum)

    Task.fromResult addResult

Full Code

app [main] {
    pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.16.0/O00IPk-Krg_diNS2dVWlI0ZQP794Vctxzv0ha96mK0E.tar.br",
}

import pf.Stdin
import pf.Stdout
import pf.Stderr

main = run |> Task.onErr printErr

run : Task {} _
run =
    Stdout.line! "Enter some numbers on different lines, then press Ctrl-D to sum them up."

    sum = Task.loop! 0 addNumberFromStdin
    Stdout.line! "Sum: $(Num.toStr sum)"

addNumberFromStdin : I64 -> Task [Done I64, Step I64] _
addNumberFromStdin = \sum ->
    when Stdin.line |> Task.result! is
        Ok input ->
            when Str.toI64 input is
                Ok num -> Task.ok (Step (sum + num))
                Err _ -> Task.err (NotNum input)

        Err (StdinErr EndOfFile) -> Task.ok (Done sum)
        Err err -> err |> Inspect.toStr |> NotNum |> Task.err

printErr : _ -> Task {} _
printErr = \err ->
    when err is
        NotNum text -> Stderr.line "Error: \"$(text)\" is not a valid I64 number."
        _ -> Stderr.line "Error: $(Inspect.toStr err)"

Output

Run this from the directory that has main.roc in it:

$ roc main.roc < numbers.txt 
Enter some numbers on different lines, then press Ctrl-D to sum them up.
Sum: 178