Safe Maths
This example shows how to perform calculations while avoiding overflows.
For example; +
actually uses Num.add
, which can crash if the bytes of the result can not fit in the provided type:
» Num.max_u64 + Num.max_u64 This Roc code crashed with: "Integer addition overflowed!" * : U64
If you want to avoid a program-ending crash, you can instead use:
» Num.add_checked(Num.max_u64, Num.max_u64) Err Overflow : Result U64 [Overflow]
That would allow you to display a clean error to the user or handle the failure in an intelligent way, so that your user does not lose all their progress!
Use checked
math functions if reliability is important for your application.
For a realistic demonstration, we will use checked
math functions to calculate the variance of a population.
The variance formula is: σ² = ∑(X - µ)² / N
where:
σ²
= varianceX
= each elementµ
= mean of elementsN
= length of list
Code
app [main!] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br" } import cli.Stdout import cli.Arg exposing [Arg] ## Safely calculates the variance of a population. ## ## variance formula: σ² = ∑(X - µ)² / N ## ## σ² = variance ## X = each element ## µ = mean of elements ## N = length of list ## ## Performance note: safe or checked math prevents crashes but also runs slower. ## safe_variance : List (Frac a) -> Result (Frac a) [EmptyInputList, Overflow] safe_variance = |maybe_empty_list| # Check length to prevent division by zero when List.len(maybe_empty_list) is 0 -> Err(EmptyInputList) _ -> non_empty_list = maybe_empty_list # Length of list as a fraction (for compatibility in division) n = non_empty_list |> List.len |> Num.to_frac mean = non_empty_list # sum of all elements: |> List.walk_try(0.0, |state, elem| Num.add_checked(state, elem)) |> Result.map_ok(|x| x / n)? non_empty_list |> List.walk_try( 0.0, |state, elem| diff = Num.sub_checked(elem, mean)? # (X - µ) squared = Num.mul_checked(diff, diff)? # (X - µ)² Num.add_checked(squared, state), # ∑ ) |> Result.map_ok(|x| x / n) main! : List Arg => Result {} _ main! = |_args| variance_result = [46, 69, 32, 60, 52, 41] |> safe_variance |> Result.map_ok(Num.to_str) |> Result.map_ok(|v| "σ² = ${v}") output_str = when variance_result is Ok(str) -> str Err(EmptyInputList) -> "Error: EmptyInputList: I can't calculate the variance over an empty list." Err(Overflow) -> "Error: Overflow: When calculating the variance, a number got too large to store in the available memory for the type." Stdout.line!(output_str) expect safe_variance([]) == Err(EmptyInputList) expect safe_variance([0]) == Ok(0) expect safe_variance([100]) == Ok(0) expect safe_variance([4, 22, 99, 204, 18, 20]) == Ok(5032.138888888888888888) expect safe_variance([46, 69, 32, 60, 52, 41]) == Ok(147.666666666666666666)
Output
Run this from the directory that has main.roc
in it:
$ roc main.roc σ² = 147.666666666666666666
Run unit tests with roc test main.roc