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).
For a game loop example, check out snake.
Full Code
app [main!] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.21.0/4rAQg8kUYZ3Vksr4qMQHpaFYNiHSn9GgS7gVxghd1XYV.tar.zst", roc: "nightly-2026-08-12-606470f", } import cli.Stdin import cli.Stdout import cli.Stderr import cli.OsStr ## recursive function that sums every number that is provided through stdin add_number_from_stdin! : I64 => Try(I64, _) add_number_from_stdin! = |sum| { match Stdin.line!() { Ok(input) => { num = I64.from_str(input) ? |_| NotNum(input) add_number_from_stdin!((sum + num)) } Err(EndOfFile) => Ok(sum) Err(err) => Err(NotNum(Str.inspect(err))) } } run! : () => Try({}, _) 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: ${sum.to_str()}") } main! : List(OsStr) => Try({}, [Exit(I32), ..]) main! = |_args| { match run!() { Ok({}) => Ok({}) Err(NotNum(text)) => { _ = Stderr.line!("Error: \"${text}\" is not a valid I64 number.") Err(Exit(1)) } Err(err) => { _ = Stderr.line!("Error: ${Str.inspect(err)}") Err(Exit(1)) } } }
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