Safe Maths
This example shows how to perform calculations while avoiding overflows.
For example; + actually uses plus, which can crash if the bytes of the result can not fit in the provided type:
» U64.highest + U64.highest This Roc code crashed with: "Integer addition overflowed!" * : U64
If you want to avoid a program-ending crash, you can instead use plus_try:
» U64.highest.plus_try(U64.highest) Err(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 *_try math functions if reliability is important for your application.
For a realistic demonstration, we will use *_try 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
## 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(Dec) -> Try(Dec, [EmptyInputList, Overflow]) safe_variance = |maybe_empty_list| { # Check length to prevent division by zero match maybe_empty_list.len() { 0 => Err(EmptyInputList) _ => { non_empty_list = maybe_empty_list # Length of list as a Dec (for compatibility in division) n = non_empty_list.len().to_dec() mean = non_empty_list # sum of all elements: .fold_try(0.0, |state, elem| elem.plus_try(state)) .map_ok(|x| x / n)? non_empty_list .fold_try( 0.0, |state, elem| { diff = elem.minus_try(mean)? # (X - µ) squared = times_try(diff, diff)? # (X - µ)² squared.plus_try(state) # ∑ }, ) .map_ok(|x| x / n) } } } main! = |_| { variance_result = ( [46, 69, 32, 60, 52, 41] |> safe_variance ).map_ok(|v| v.to_str()) .map_ok(|v| "σ² = ${v}") output_str = match variance_result { 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." } echo!("${output_str}\n") Ok({}) } 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) # The following function should soon be available in the Roc builtins times_try : Dec, Dec -> Try(Dec, [Overflow, ..]) times_try = |a, b| { result = a.times_saturated(b) if result == Dec.lowest or result == Dec.highest { # For simplicity, some edge cases are ignored here, such as # Dec.highest.times_try(1) Err(Overflow) } else { Ok(result) } }
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:
$ roc test main.roc All (5) tests passed in 19.9 ms.