Loop Effects

Sometimes, you need to repeat an effectful function, multiple times until a particular event occurs. In Roc, you can use a recursive function to do this.

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

Full Code

app [main!] {
    cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.19.0/Hj-J_zxz7V9YurCSTFcFdu6cQJie4guzsPMUi5kBYUk.tar.br",
}

import cli.Stdin
import cli.Stdout
import cli.Stderr

main! = |_args|
    when run!({}) is
        Ok({}) -> Ok({})
        Err(err) -> print_err!(err)

run! : {} => Result {} _
run! = |_|
    Stdout.line!("Enter some numbers on different lines, then press Ctrl-D to sum them up.")?

    sum = add_number_from_stdin!(0)?

    Stdout.line!("Sum: ${Num.to_str(sum)}")

## recursive function that sums every number that is provided through stdin
add_number_from_stdin! : I64 => Result I64 _
add_number_from_stdin! = |sum|
    when Stdin.line!({}) is
        Ok(input) ->
            when Str.to_i64(input) is
                Ok(num) -> add_number_from_stdin!((sum + num))
                Err(_) -> Err(NotNum(input))

        Err(EndOfFile) -> Ok(sum)
        Err(err) -> err |> Inspect.to_str |> NotNum |> Err

print_err! : _ => Result {} _
print_err! = |err|
    when err is
        NotNum(text) -> Stderr.line!("Error: \"${text}\" is not a valid I64 number.")
        _ -> Stderr.line!("Error: ${Inspect.to_str(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