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