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.maxU64 + Num.maxU64
This Roc code crashed with: "Integer addition overflowed!"

* : U64

If you want to avoid a program-ending crash, you can instead use:

» Num.addChecked Num.maxU64 Num.maxU64

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. 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:

Code

app [main] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.16.0/O00IPk-Krg_diNS2dVWlI0ZQP794Vctxzv0ha96mK0E.tar.br" }

import cli.Stdout

## 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.
##
safeVariance : List (Frac a) -> Result (Frac a) [EmptyInputList, Overflow]
safeVariance = \maybeEmptyList ->

    # Check length to prevent DivByZero
    when List.len maybeEmptyList is
        0 -> Err EmptyInputList
        _ ->
            nonEmptyList = maybeEmptyList

            n = nonEmptyList |> List.len |> Num.toFrac

            mean =
                nonEmptyList # sum of all elements:
                |> List.walkTry 0.0 (\state, elem -> Num.addChecked state elem)
                |> Result.map (\x -> x / n)

            nonEmptyList
            |> List.walkTry
                0.0
                (\state, elem ->
                    mean
                    |> Result.try (\m -> Num.subChecked elem m) # X - µ
                    |> Result.try (\y -> Num.mulChecked y y) # ²
                    |> Result.try (\z -> Num.addChecked z state)) # ∑
            |> Result.map (\x -> x / n)

main =

    varianceResult =
        [46, 69, 32, 60, 52, 41]
        |> safeVariance
        |> Result.map Num.toStr
        |> Result.map (\v -> "σ² = $(v)")

    outputStr =
        when varianceResult 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 outputStr

expect (safeVariance []) == Err EmptyInputList
expect (safeVariance [0]) == Ok 0
expect (safeVariance [100]) == Ok 0
expect (safeVariance [4, 22, 99, 204, 18, 20]) == Ok 5032.138888888888888888
expect (safeVariance [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