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.
- If this new Task matches
Task.ok (Step newState)
then the loop continues with a new call of the same function, now usingnewState
. - If the Task is of the form
Task.ok (Done finalState)
then the loop stops and returnsfinalState
. - If the Task is a
Task.err err
, then the loop stops and returns the error.
Code step by step
Task.loop 0 addNumberFromStdin
We provide loop
with:
- our initial state; the number 0
- a function that can ingest a number and return a Task;
addNumberFromStdin
This function takes our current sum total, reads a line from stdin and returns one of the following:
Task.ok (Step newSum)
Task.ok (Done finalSum)
on Ctrl-D or end of file.Task.err (NotNum Str)
if something other than a number was provided to stdin.
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