From 2bd3b76a792fcf45ee9ee8f2a3d5cd6b6ac27e72 Mon Sep 17 00:00:00 2001
From: Hayleigh Thompson <me@hayleigh.dev>
Date: Sat, 17 Aug 2024 12:37:00 +0100
Subject: [PATCH] :recycle: Replace error types with Nil for better DX with
 other error-producing functions.

---
 src/gleam_community/maths/arithmetics.gleam   |  94 +++--
 src/gleam_community/maths/combinatorics.gleam | 188 ++++-----
 src/gleam_community/maths/elementary.gleam    | 191 +++------
 src/gleam_community/maths/metrics.gleam       | 373 ++++++++----------
 src/gleam_community/maths/piecewise.gleam     |  78 ++--
 src/gleam_community/maths/predicates.gleam    |  74 ++--
 src/gleam_community/maths/sequences.gleam     |  60 ++-
 src/gleam_community/maths/special.gleam       |  16 +-
 .../maths/predicates_test.gleam               |   2 +-
 9 files changed, 450 insertions(+), 626 deletions(-)

diff --git a/src/gleam_community/maths/arithmetics.gleam b/src/gleam_community/maths/arithmetics.gleam
index dfa3c8d..d7e0e73 100644
--- a/src/gleam_community/maths/arithmetics.gleam
+++ b/src/gleam_community/maths/arithmetics.gleam
@@ -20,11 +20,11 @@
 ////<style>
 ////    .katex { font-size: 1.1em; }
 ////</style>
-//// 
+////
 //// ---
-//// 
+////
 //// Arithmetics: A module containing a collection of fundamental mathematical functions relating to simple arithmetics (addition, subtraction, multiplication, etc.), but also number theory.
-//// 
+////
 //// * **Division functions**
 ////   * [`gcd`](#gcd)
 ////   * [`lcm`](#lcm)
@@ -40,7 +40,7 @@
 ////   * [`int_cumulative_sum`](#int_cumulative_sum)
 ////   * [`float_cumulative_product`](#float_cumulative_product)
 ////   * [`int_cumulative_product`](#int_cumulative_product)
-//// 
+////
 
 import gleam/int
 import gleam/list
@@ -57,7 +57,7 @@ import gleam_community/maths/piecewise
 ///     </a>
 /// </div>
 ///
-/// The function calculates the greatest common divisor of two integers 
+/// The function calculates the greatest common divisor of two integers
 /// \\(x, y \in \mathbb{Z}\\). The greatest common divisor is the largest positive
 /// integer that is divisible by both \\(x\\) and \\(y\\).
 ///
@@ -70,7 +70,7 @@ import gleam_community/maths/piecewise
 ///     pub fn example() {
 ///       arithmetics.gcd(1, 1)
 ///       |> should.equal(1)
-///   
+///
 ///       arithmetics.gcd(100, 10)
 ///       |> should.equal(10)
 ///
@@ -107,24 +107,24 @@ fn do_gcd(x: Int, y: Int) -> Int {
 ///     </a>
 /// </div>
 ///
-/// 
+///
 /// Given two integers, \\(x\\) (dividend) and \\(y\\) (divisor), the Euclidean modulo
-/// of \\(x\\) by \\(y\\), denoted as \\(x \mod y\\), is the remainder \\(r\\) of the 
+/// of \\(x\\) by \\(y\\), denoted as \\(x \mod y\\), is the remainder \\(r\\) of the
 /// division of \\(x\\) by \\(y\\), such that:
-/// 
+///
 /// \\[
 /// x = q \cdot y + r \quad \text{and} \quad 0 \leq r < |y|,
 /// \\]
-/// 
+///
 /// where \\(q\\) is an integer that represents the quotient of the division.
 ///
-/// The Euclidean modulo function of two numbers, is the remainder operation most 
-/// commonly utilized in mathematics. This differs from the standard truncating 
-/// modulo operation frequently employed in programming via the `%` operator. 
-/// Unlike the `%` operator, which may return negative results depending on the 
-/// divisor's sign, the Euclidean modulo function is designed to always yield a 
+/// The Euclidean modulo function of two numbers, is the remainder operation most
+/// commonly utilized in mathematics. This differs from the standard truncating
+/// modulo operation frequently employed in programming via the `%` operator.
+/// Unlike the `%` operator, which may return negative results depending on the
+/// divisor's sign, the Euclidean modulo function is designed to always yield a
 /// positive outcome, ensuring consistency with mathematical conventions.
-/// 
+///
 /// Note that like the Gleam division operator `/` this will return `0` if one of
 /// the arguments is `0`.
 ///
@@ -138,7 +138,7 @@ fn do_gcd(x: Int, y: Int) -> Int {
 ///     pub fn example() {
 ///       arithmetics.euclidean_modulo(15, 4)
 ///       |> should.equal(3)
-///   
+///
 ///       arithmetics.euclidean_modulo(-3, -2)
 ///       |> should.equal(1)
 ///
@@ -168,7 +168,7 @@ pub fn int_euclidean_modulo(x: Int, y: Int) -> Int {
 ///     </a>
 /// </div>
 ///
-/// The function calculates the least common multiple of two integers 
+/// The function calculates the least common multiple of two integers
 /// \\(x, y \in \mathbb{Z}\\). The least common multiple is the smallest positive
 /// integer that has both \\(x\\) and \\(y\\) as factors.
 ///
@@ -181,7 +181,7 @@ pub fn int_euclidean_modulo(x: Int, y: Int) -> Int {
 ///     pub fn example() {
 ///       arithmetics.lcm(1, 1)
 ///       |> should.equal(1)
-///   
+///
 ///       arithmetics.lcm(100, 10)
 ///       |> should.equal(100)
 ///
@@ -208,7 +208,7 @@ pub fn lcm(x: Int, y: Int) -> Int {
 ///     </a>
 /// </div>
 ///
-/// The function returns all the positive divisors of an integer, including the 
+/// The function returns all the positive divisors of an integer, including the
 /// number itself.
 ///
 /// <details>
@@ -260,7 +260,7 @@ fn find_divisors(n: Int) -> List(Int) {
 ///     </a>
 /// </div>
 ///
-/// The function returns all the positive divisors of an integer, excluding the 
+/// The function returns all the positive divisors of an integer, excluding the
 /// number iteself.
 ///
 /// <details>
@@ -362,7 +362,7 @@ pub fn float_sum(arr: List(Float), weights: option.Option(List(Float))) -> Float
 /// \sum_{i=1}^n x_i
 /// \\]
 ///
-/// In the formula, \\(n\\) is the length of the list and \\(x_i \in \mathbb{Z}\\) is 
+/// In the formula, \\(n\\) is the length of the list and \\(x_i \in \mathbb{Z}\\) is
 /// the value in the input list indexed by \\(i\\).
 ///
 /// <details>
@@ -414,7 +414,7 @@ pub fn int_sum(arr: List(Int)) -> Int {
 /// In the formula, \\(n\\) is the length of the list and \\(x_i \in \mathbb{R}\\) is
 /// the value in the input list indexed by \\(i\\), while the \\(w_i \in \mathbb{R}\\)
 /// are corresponding weights (\\(w_i = 1.0\\;\forall i=1...n\\) by default).
-/// 
+///
 /// <details>
 ///     <summary>Example:</summary>
 ///
@@ -444,7 +444,7 @@ pub fn int_sum(arr: List(Int)) -> Int {
 pub fn float_product(
   arr: List(Float),
   weights: option.Option(List(Float)),
-) -> Result(Float, String) {
+) -> Result(Float, Nil) {
   case arr, weights {
     [], _ ->
       1.0
@@ -454,22 +454,16 @@ pub fn float_product(
       |> list.fold(1.0, fn(acc: Float, a: Float) -> Float { a *. acc })
       |> Ok
     _, option.Some(warr) -> {
-      let results =
-        list.zip(arr, warr)
-        |> list.map(fn(a: #(Float, Float)) -> Result(Float, String) {
-          pair.first(a)
-          |> elementary.power(pair.second(a))
-        })
-        |> result.all
-      case results {
-        Ok(prods) ->
-          prods
-          |> list.fold(1.0, fn(acc: Float, a: Float) -> Float { a *. acc })
-          |> Ok
-        Error(msg) ->
-          msg
-          |> Error
-      }
+      list.zip(arr, warr)
+      |> list.map(fn(a: #(Float, Float)) -> Result(Float, Nil) {
+        pair.first(a)
+        |> elementary.power(pair.second(a))
+      })
+      |> result.all
+      |> result.map(fn(prods) {
+        prods
+        |> list.fold(1.0, fn(acc: Float, a: Float) -> Float { a *. acc })
+      })
     }
   }
 }
@@ -486,7 +480,7 @@ pub fn float_product(
 /// \prod_{i=1}^n x_i
 /// \\]
 ///
-/// In the formula, \\(n\\) is the length of the list and \\(x_i \in \mathbb{Z}\\) is 
+/// In the formula, \\(n\\) is the length of the list and \\(x_i \in \mathbb{Z}\\) is
 /// the value in the input list indexed by \\(i\\).
 ///
 /// <details>
@@ -536,7 +530,7 @@ pub fn int_product(arr: List(Int)) -> Int {
 /// \\]
 ///
 /// In the formula, \\(v_j\\) is the \\(j\\)'th element in the cumulative sum of \\(n\\)
-/// elements. That is, \\(n\\) is the length of the list and \\(x_i \in \mathbb{R}\\) 
+/// elements. That is, \\(n\\) is the length of the list and \\(x_i \in \mathbb{R}\\)
 /// is the value in the input list indexed by \\(i\\). The value \\(v_j\\) is thus the
 /// sum of the \\(1\\) to \\(j\\) first elements in the given list.
 ///
@@ -586,7 +580,7 @@ pub fn float_cumulative_sum(arr: List(Float)) -> List(Float) {
 /// \\]
 ///
 /// In the formula, \\(v_j\\) is the \\(j\\)'th element in the cumulative sum of \\(n\\)
-/// elements. That is, \\(n\\) is the length of the list and \\(x_i \in \mathbb{Z}\\) 
+/// elements. That is, \\(n\\) is the length of the list and \\(x_i \in \mathbb{Z}\\)
 /// is the value in the input list indexed by \\(i\\). The value \\(v_j\\) is thus the
 /// sum of the \\(1\\) to \\(j\\) first elements in the given list.
 ///
@@ -635,10 +629,10 @@ pub fn int_cumulative_sum(arr: List(Int)) -> List(Int) {
 /// v_j = \prod_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n
 /// \\]
 ///
-/// In the formula, \\(v_j\\) is the \\(j\\)'th element in the cumulative product of 
-/// \\(n\\) elements. That is, \\(n\\) is the length of the list and 
-/// \\(x_i \in \mathbb{R}\\) is the value in the input list indexed by \\(i\\). The 
-/// value \\(v_j\\) is thus the sum of the \\(1\\) to \\(j\\) first elements in the 
+/// In the formula, \\(v_j\\) is the \\(j\\)'th element in the cumulative product of
+/// \\(n\\) elements. That is, \\(n\\) is the length of the list and
+/// \\(x_i \in \mathbb{R}\\) is the value in the input list indexed by \\(i\\). The
+/// value \\(v_j\\) is thus the sum of the \\(1\\) to \\(j\\) first elements in the
 /// given list.
 ///
 /// <details>
@@ -687,9 +681,9 @@ pub fn float_cumulative_product(arr: List(Float)) -> List(Float) {
 /// v_j = \prod_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n
 /// \\]
 ///
-/// In the formula, \\(v_j\\) is the \\(j\\)'th element in the cumulative product of 
-/// \\(n\\) elements. That is, \\(n\\) is the length of the list and 
-/// \\(x_i \in \mathbb{Z}\\) is the value in the input list indexed by \\(i\\). The 
+/// In the formula, \\(v_j\\) is the \\(j\\)'th element in the cumulative product of
+/// \\(n\\) elements. That is, \\(n\\) is the length of the list and
+/// \\(x_i \in \mathbb{Z}\\) is the value in the input list indexed by \\(i\\). The
 /// value \\(v_j\\) is thus the product of the \\(1\\) to \\(j\\) first elements in the
 /// given list.
 ///
diff --git a/src/gleam_community/maths/combinatorics.gleam b/src/gleam_community/maths/combinatorics.gleam
index b026c4d..f191eef 100644
--- a/src/gleam_community/maths/combinatorics.gleam
+++ b/src/gleam_community/maths/combinatorics.gleam
@@ -20,12 +20,12 @@
 ////<style>
 ////    .katex { font-size: 1.1em; }
 ////</style>
-//// 
+////
 //// ---
-//// 
-//// Combinatorics: A module that offers mathematical functions related to counting, arrangements, 
-//// and permutations/combinations. 
-//// 
+////
+//// Combinatorics: A module that offers mathematical functions related to counting, arrangements,
+//// and permutations/combinations.
+////
 //// * **Combinatorial functions**
 ////   * [`combination`](#combination)
 ////   * [`factorial`](#factorial)
@@ -33,7 +33,7 @@
 ////   * [`list_combination`](#list_combination)
 ////   * [`list_permutation`](#list_permutation)
 ////   * [`cartesian_product`](#cartesian_product)
-//// 
+////
 
 import gleam/iterator
 import gleam/list
@@ -70,26 +70,26 @@ pub type CombinatoricsMode {
 /// Also known as the "stars and bars" problem in combinatorics.
 ///
 /// The implementation uses an efficient iterative multiplicative formula for computing the result.
-/// 
+///
 /// <details>
 /// <summary>Details</summary>
-/// 
-/// A \\(k\\)-combination is a sequence of \\(k\\) elements selected from \\(n\\) elements where 
-/// the order of selection does not matter. For example, consider selecting 2 elements from a list 
+///
+/// A \\(k\\)-combination is a sequence of \\(k\\) elements selected from \\(n\\) elements where
+/// the order of selection does not matter. For example, consider selecting 2 elements from a list
 /// of 3 elements: `["A", "B", "C"]`:
-/// 
-/// - For \\(k\\)-combinations (without repetitions), where order does not matter, the possible 
+///
+/// - For \\(k\\)-combinations (without repetitions), where order does not matter, the possible
 ///   selections are:
 ///   - `["A", "B"]`
 ///   - `["A", "C"]`
 ///   - `["B", "C"]`
 ///
-/// - For \\(k\\)-combinations (with repetitions), where order does not matter but elements can 
+/// - For \\(k\\)-combinations (with repetitions), where order does not matter but elements can
 ///   repeat, the possible selections are:
 ///   - `["A", "A"], ["A", "B"], ["A", "C"]`
 ///   - `["B", "B"], ["B", "C"], ["C", "C"]`
 ///
-/// - On the contrary, for \\(k\\)-permutations (without repetitions), the order matters, so the 
+/// - On the contrary, for \\(k\\)-permutations (without repetitions), the order matters, so the
 ///   possible selections are:
 ///   - `["A", "B"], ["B", "A"]`
 ///   - `["A", "C"], ["C", "A"]`
@@ -106,15 +106,15 @@ pub type CombinatoricsMode {
 ///       // Invalid input gives an error
 ///       combinatorics.combination(-1, 1, option.None)
 ///       |> should.be_error()
-///     
+///
 ///       // Valid input: n = 4 and k = 0
 ///       combinatorics.combination(4, 0, option.Some(combinatorics.WithoutRepetitions))
 ///       |> should.equal(Ok(1))
-///     
+///
 ///       // Valid input: k = n (n = 4, k = 4)
 ///       combinatorics.combination(4, 4, option.Some(combinatorics.WithoutRepetitions))
 ///       |> should.equal(Ok(1))
-///     
+///
 ///       // Valid input: combinations with repetition (n = 2, k = 3)
 ///       combinatorics.combination(2, 3, option.Some(combinatorics.WithRepetitions))
 ///       |> should.equal(Ok(4))
@@ -125,17 +125,15 @@ pub type CombinatoricsMode {
 ///         <small>Back to top ↑</small>
 ///     </a>
 /// </div>
-/// 
+///
 pub fn combination(
   n: Int,
   k: Int,
   mode: option.Option(CombinatoricsMode),
-) -> Result(Int, String) {
+) -> Result(Int, Nil) {
   case n, k {
-    _, _ if n < 0 ->
-      "Invalid input argument: n < 0. Valid input is n >= 0." |> Error
-    _, _ if k < 0 ->
-      "Invalid input argument: k < 0. Valid input is k >= 0." |> Error
+    _, _ if n < 0 -> Error(Nil)
+    _, _ if k < 0 -> Error(Nil)
     _, _ -> {
       case mode {
         option.Some(WithRepetitions) -> combination_with_repetitions(n, k)
@@ -145,12 +143,11 @@ pub fn combination(
   }
 }
 
-fn combination_with_repetitions(n: Int, k: Int) -> Result(Int, String) {
-  { n + k - 1 }
-  |> combination_without_repetitions(k)
+fn combination_with_repetitions(n: Int, k: Int) -> Result(Int, Nil) {
+  combination_without_repetitions(n + k - 1, k)
 }
 
-fn combination_without_repetitions(n: Int, k: Int) -> Result(Int, String) {
+fn combination_without_repetitions(n: Int, k: Int) -> Result(Int, Nil) {
   case n, k {
     _, _ if k == 0 || k == n -> {
       1 |> Ok
@@ -202,17 +199,11 @@ fn combination_without_repetitions(n: Int, k: Int) -> Result(Int, String) {
 ///     </a>
 /// </div>
 ///
-pub fn factorial(n) -> Result(Int, String) {
+pub fn factorial(n) -> Result(Int, Nil) {
   case n {
-    _ if n < 0 ->
-      "Invalid input argument: n < 0. Valid input is n >= 0."
-      |> Error
-    0 ->
-      1
-      |> Ok
-    1 ->
-      1
-      |> Ok
+    _ if n < 0 -> Error(Nil)
+    0 -> Ok(1)
+    1 -> Ok(1)
     _ ->
       list.range(1, n)
       |> list.fold(1, fn(acc: Int, x: Int) -> Int { acc * x })
@@ -227,50 +218,50 @@ pub fn factorial(n) -> Result(Int, String) {
 /// </div>
 ///
 /// A combinatorial function for computing the number of \\(k\\)-permutations.
-/// 
+///
 /// **Without** repetitions:
 ///
 /// \\[
 /// P(n, k) = \binom{n}{k} \cdot k! = \frac{n!}{(n - k)!}
 /// \\]
-/// 
+///
 /// **With** repetitions:
-/// 
+///
 /// \\[
 /// P^*(n, k) = n^k
 /// \\]
-/// 
+///
 /// The implementation uses an efficient iterative multiplicative formula for computing the result.
-/// 
+///
 /// <details>
 /// <summary>Details</summary>
-/// 
+///
 /// A \\(k\\)-permutation (without repetitions) is a sequence of \\(k\\) elements selected from \
-/// \\(n\\) elements where the order of selection matters. For example, consider selecting 2 
+/// \\(n\\) elements where the order of selection matters. For example, consider selecting 2
 /// elements from a list of 3 elements: `["A", "B", "C"]`:
-/// 
-/// - For \\(k\\)-permutations (without repetitions), the order matters, so the possible selections 
+///
+/// - For \\(k\\)-permutations (without repetitions), the order matters, so the possible selections
 /// are:
 ///   - `["A", "B"], ["B", "A"]`
 ///   - `["A", "C"], ["C", "A"]`
 ///   - `["B", "C"], ["C", "B"]`
-/// 
-/// - For \\(k\\)-permutations (with repetitions), the order also matters, but we have repeated 
+///
+/// - For \\(k\\)-permutations (with repetitions), the order also matters, but we have repeated
 ///   selections:
 ///   - `["A", "A"], ["A", "B"], ["A", "C"]`
 ///   - `["B", "A"], ["B", "B"], ["B", "C"]`
 ///   - `["C", "A"], ["C", "B"], ["C", "C"]`
 ///
-/// - On the contrary, for \\(k\\)-combinations (without repetitions), where order does not matter, 
+/// - On the contrary, for \\(k\\)-combinations (without repetitions), where order does not matter,
 ///   the possible selections are:
 ///   - `["A", "B"]`
 ///   - `["A", "C"]`
 ///   - `["B", "C"]`
 /// </details>
-/// 
+///
 /// <details>
 ///     <summary>Example:</summary>
-/// 
+///
 ///     import gleam/option
 ///     import gleeunit/should
 ///     import gleam_community/maths/combinatorics
@@ -300,22 +291,16 @@ pub fn permutation(
   n: Int,
   k: Int,
   mode: option.Option(CombinatoricsMode),
-) -> Result(Int, String) {
-  case n, k {
-    _, _ if n < 0 ->
-      "Invalid input argument: n < 0. Valid input is n >= 0." |> Error
-    _, _ if k < 0 ->
-      "Invalid input argument: k < 0. Valid input is k >= 0." |> Error
-    _, _ -> {
-      case mode {
-        option.Some(WithRepetitions) -> permutation_with_repetitions(n, k)
-        _ -> permutation_without_repetitions(n, k)
-      }
-    }
+) -> Result(Int, Nil) {
+  case n, k, mode {
+    _, _, _ if n < 0 -> Error(Nil)
+    _, _, _ if k < 0 -> Error(Nil)
+    _, _, option.Some(WithRepetitions) -> permutation_with_repetitions(n, k)
+    _, _, _ -> permutation_without_repetitions(n, k)
   }
 }
 
-fn permutation_without_repetitions(n: Int, k: Int) -> Result(Int, String) {
+fn permutation_without_repetitions(n: Int, k: Int) -> Result(Int, Nil) {
   case n, k {
     _, _ if k < 0 || k > n -> {
       0 |> Ok
@@ -330,7 +315,7 @@ fn permutation_without_repetitions(n: Int, k: Int) -> Result(Int, String) {
   }
 }
 
-fn permutation_with_repetitions(n: Int, k: Int) -> Result(Int, String) {
+fn permutation_with_repetitions(n: Int, k: Int) -> Result(Int, Nil) {
   let n_float = conversion.int_to_float(n)
   let k_float = conversion.int_to_float(k)
   // 'n' ank 'k' are positive integers, so no errors here...
@@ -346,11 +331,11 @@ fn permutation_with_repetitions(n: Int, k: Int) -> Result(Int, String) {
 ///     </a>
 /// </div>
 ///
-/// Generates all possible combinations of \\(k\\) elements selected from a given list of size 
+/// Generates all possible combinations of \\(k\\) elements selected from a given list of size
 /// \\(n\\).
 ///
-/// The function can handle cases with and without repetitions 
-/// (see more details [here](#combination)). Also, note that repeated elements are treated as 
+/// The function can handle cases with and without repetitions
+/// (see more details [here](#combination)). Also, note that repeated elements are treated as
 /// distinct.
 ///
 /// <details>
@@ -370,7 +355,7 @@ fn permutation_with_repetitions(n: Int, k: Int) -> Result(Int, String) {
 ///           3,
 ///           option.Some(combinatorics.WithoutRepetitions),
 ///         )
-///     
+///
 ///       result
 ///       |> iterator.to_list()
 ///       |> set.from_list()
@@ -388,29 +373,20 @@ pub fn list_combination(
   arr: List(a),
   k: Int,
   mode: option.Option(CombinatoricsMode),
-) -> Result(iterator.Iterator(List(a)), String) {
-  case k {
-    _ if k < 0 ->
-      "Invalid input argument: k < 0. Valid input is k >= 0."
-      |> Error
-    _ ->
-      case mode {
-        option.Some(WithRepetitions) ->
-          list_combination_with_repetitions(arr, k)
-        _ -> list_combination_without_repetitions(arr, k)
-      }
+) -> Result(iterator.Iterator(List(a)), Nil) {
+  case k, mode {
+    _, _ if k < 0 -> Error(Nil)
+    _, option.Some(WithRepetitions) -> list_combination_with_repetitions(arr, k)
+    _, _ -> list_combination_without_repetitions(arr, k)
   }
 }
 
 fn list_combination_without_repetitions(
   arr: List(a),
   k: Int,
-) -> Result(iterator.Iterator(List(a)), String) {
+) -> Result(iterator.Iterator(List(a)), Nil) {
   case k, list.length(arr) {
-    _, arr_length if k > arr_length -> {
-      "Invalid input argument: k > length(arr). Valid input is 0 <= k <= length(arr)."
-      |> Error
-    }
+    _, arr_length if k > arr_length -> Error(Nil)
     // Special case: When k = n, then the entire list is the only valid combination
     _, arr_length if k == arr_length -> {
       iterator.single(arr) |> Ok
@@ -446,7 +422,7 @@ fn do_list_combination_without_repetitions(
 fn list_combination_with_repetitions(
   arr: List(a),
   k: Int,
-) -> Result(iterator.Iterator(List(a)), String) {
+) -> Result(iterator.Iterator(List(a)), Nil) {
   Ok(do_list_combination_with_repetitions(iterator.from_list(arr), k, []))
 }
 
@@ -476,11 +452,11 @@ fn do_list_combination_with_repetitions(
 ///     </a>
 /// </div>
 ///
-/// Generates all possible permutations of \\(k\\) elements selected from a given list of size 
+/// Generates all possible permutations of \\(k\\) elements selected from a given list of size
 /// \\(n\\).
 ///
-/// The function can handle cases with and without repetitions 
-/// (see more details [here](#permutation)). Also, note that repeated elements are treated as 
+/// The function can handle cases with and without repetitions
+/// (see more details [here](#permutation)). Also, note that repeated elements are treated as
 /// distinct.
 ///
 /// <details>
@@ -500,7 +476,7 @@ fn do_list_combination_with_repetitions(
 ///           3,
 ///           option.Some(combinatorics.WithoutRepetitions),
 ///         )
-///     
+///
 ///       result
 ///       |> iterator.to_list()
 ///       |> set.from_list()
@@ -523,22 +499,17 @@ fn do_list_combination_with_repetitions(
 ///     </a>
 /// </div>
 ///
-/// 
+///
 pub fn list_permutation(
   arr: List(a),
   k: Int,
   mode: option.Option(CombinatoricsMode),
-) -> Result(iterator.Iterator(List(a)), String) {
-  case k {
-    _ if k < 0 ->
-      "Invalid input argument: k < 0. Valid input is k >= 0."
-      |> Error
-    _ ->
-      case mode {
-        option.Some(WithRepetitions) ->
-          list_permutation_with_repetitions(arr, k)
-        _ -> list_permutation_without_repetitions(arr, k)
-      }
+) -> Result(iterator.Iterator(List(a)), Nil) {
+  case k, mode {
+    _, _ if k < 0 -> Error(Nil)
+    _, option.Some(WithRepetitions) ->
+      Ok(list_permutation_with_repetitions(arr, k))
+    _, _ -> list_permutation_without_repetitions(arr, k)
   }
 }
 
@@ -558,12 +529,9 @@ fn remove_first_by_index(
 fn list_permutation_without_repetitions(
   arr: List(a),
   k: Int,
-) -> Result(iterator.Iterator(List(a)), String) {
+) -> Result(iterator.Iterator(List(a)), Nil) {
   case k, list.length(arr) {
-    _, arr_length if k > arr_length -> {
-      "Invalid input argument: k > length(arr). Valid input is 0 <= k <= length(arr)."
-      |> Error
-    }
+    _, arr_length if k > arr_length -> Error(Nil)
     _, _ -> {
       let indexed_arr = list.index_map(arr, fn(x, i) { #(i, x) })
       Ok(do_list_permutation_without_repetitions(
@@ -594,9 +562,9 @@ fn do_list_permutation_without_repetitions(
 fn list_permutation_with_repetitions(
   arr: List(a),
   k: Int,
-) -> Result(iterator.Iterator(List(a)), String) {
+) -> iterator.Iterator(List(a)) {
   let indexed_arr = list.index_map(arr, fn(x, i) { #(i, x) })
-  Ok(do_list_permutation_with_repetitions(indexed_arr, k))
+  do_list_permutation_with_repetitions(indexed_arr, k)
 }
 
 fn do_list_permutation_with_repetitions(
@@ -636,7 +604,7 @@ fn do_list_permutation_with_repetitions(
 ///       set.from_list([])
 ///       |> combinatorics.cartesian_product(set.from_list([]))
 ///       |> should.equal(set.from_list([]))
-///     
+///
 ///       // Cartesian product of two sets with numeric values
 ///       set.from_list([1.0, 10.0])
 ///       |> combinatorics.cartesian_product(set.from_list([1.0, 2.0]))
diff --git a/src/gleam_community/maths/elementary.gleam b/src/gleam_community/maths/elementary.gleam
index 25e6b47..d5ff8af 100644
--- a/src/gleam_community/maths/elementary.gleam
+++ b/src/gleam_community/maths/elementary.gleam
@@ -20,11 +20,11 @@
 ////<style>
 ////    .katex { font-size: 1.1em; }
 ////</style>
-//// 
+////
 //// ---
-//// 
+////
 //// Elementary: A module containing a comprehensive set of foundational mathematical functions and constants.
-//// 
+////
 //// * **Trigonometric and hyperbolic functions**
 ////   * [`acos`](#acos)
 ////   * [`acosh`](#acosh)
@@ -53,7 +53,7 @@
 ////   * [`pi`](#pi)
 ////   * [`tau`](#tau)
 ////   * [`e`](#e)
-//// 
+////
 
 import gleam/int
 import gleam/option
@@ -98,14 +98,10 @@ import gleam/option
 ///     </a>
 /// </div>
 ///
-pub fn acos(x: Float) -> Result(Float, String) {
+pub fn acos(x: Float) -> Result(Float, Nil) {
   case x >=. -1.0 && x <=. 1.0 {
-    True ->
-      do_acos(x)
-      |> Ok
-    False ->
-      "Invalid input argument: x >= -1 or x <= 1. Valid input is -1. <= x <= 1."
-      |> Error
+    True -> Ok(do_acos(x))
+    False -> Error(Nil)
   }
 }
 
@@ -150,14 +146,10 @@ fn do_acos(a: Float) -> Float
 ///     </a>
 /// </div>
 ///
-pub fn acosh(x: Float) -> Result(Float, String) {
+pub fn acosh(x: Float) -> Result(Float, Nil) {
   case x >=. 1.0 {
-    True ->
-      do_acosh(x)
-      |> Ok
-    False ->
-      "Invalid input argument: x < 1. Valid input is x >= 1."
-      |> Error
+    True -> Ok(do_acosh(x))
+    False -> Error(Nil)
   }
 }
 
@@ -178,7 +170,7 @@ fn do_acosh(a: Float) -> Float
 /// \\]
 ///
 /// The function takes a number \\(x\\) in its domain \\(\[-1, 1\]\\) as input and returns a numeric
-/// value \\(y\\) that lies in the range \\(\[-\frac{\pi}{2}, \frac{\pi}{2}\]\\) (an angle in 
+/// value \\(y\\) that lies in the range \\(\[-\frac{\pi}{2}, \frac{\pi}{2}\]\\) (an angle in
 /// radians). If the input value is outside the domain of the function an error is returned.
 ///
 /// <details>
@@ -205,14 +197,10 @@ fn do_acosh(a: Float) -> Float
 ///     </a>
 /// </div>
 ///
-pub fn asin(x: Float) -> Result(Float, String) {
+pub fn asin(x: Float) -> Result(Float, Nil) {
   case x >=. -1.0 && x <=. 1.0 {
-    True ->
-      do_asin(x)
-      |> Ok
-    False ->
-      "Invalid input argument: x >= -1 or x <= 1. Valid input is -1. <= x <= 1."
-      |> Error
+    True -> Ok(do_asin(x))
+    False -> Error(Nil)
   }
 }
 
@@ -232,8 +220,8 @@ fn do_asin(a: Float) -> Float
 /// \forall x \in \(-\infty, \infty\),   \\; \sinh^{-1}{(x)} = y \in \(-\infty, +\infty\)
 /// \\]
 ///
-/// The function takes a number \\(x\\) in its domain \\(\(-\infty, +\infty\)\\) as input and 
-/// returns a numeric value \\(y\\) that lies in the range \\(\(-\infty, +\infty\)\\) (an angle in 
+/// The function takes a number \\(x\\) in its domain \\(\(-\infty, +\infty\)\\) as input and
+/// returns a numeric value \\(y\\) that lies in the range \\(\(-\infty, +\infty\)\\) (an angle in
 /// radians).
 ///
 /// <details>
@@ -274,7 +262,7 @@ fn do_asinh(a: Float) -> Float
 /// \forall x \in \(-\infty, \infty\),  \\; \tan^{-1}{(x)} = y \in \[-\frac{\pi}{2}, \frac{\pi}{2}\]
 /// \\]
 ///
-/// The function takes a number \\(x\\) in its domain \\(\(-\infty, +\infty\)\\) as input and 
+/// The function takes a number \\(x\\) in its domain \\(\(-\infty, +\infty\)\\) as input and
 /// returns a numeric value \\(y\\) that lies in the range \\(\[-\frac{\pi}{2}, \frac{\pi}{2}\]\\)
 /// (an angle in radians).
 ///
@@ -394,14 +382,10 @@ fn do_atan2(a: Float, b: Float) -> Float
 ///     </a>
 /// </div>
 ///
-pub fn atanh(x: Float) -> Result(Float, String) {
+pub fn atanh(x: Float) -> Result(Float, Nil) {
   case x >. -1.0 && x <. 1.0 {
-    True ->
-      do_atanh(x)
-      |> Ok
-    False ->
-      "Invalid input argument: x > -1 or x < 1. Valid input is -1. < x < 1."
-      |> Error
+    True -> Ok(do_atanh(x))
+    False -> Error(Nil)
   }
 }
 
@@ -421,7 +405,7 @@ fn do_atanh(a: Float) -> Float
 /// \forall x \in \(-\infty, +\infty\),   \\; \cos{(x)} = y \in \[-1, 1\]
 /// \\]
 ///
-/// The function takes a number \\(x\\) in its domain \\(\(-\infty, \infty\)\\) (an angle in 
+/// The function takes a number \\(x\\) in its domain \\(\(-\infty, \infty\)\\) (an angle in
 /// radians) as input and returns a numeric value \\(y\\) that lies in the range \\(\[-1, 1\]\\).
 ///
 /// <details>
@@ -465,8 +449,8 @@ fn do_cos(a: Float) -> Float
 /// \forall x \in \(-\infty, \infty\),   \\; \cosh{(x)} = y \in \(-\infty, +\infty\)
 /// \\]
 ///
-/// The function takes a number \\(x\\) in its domain \\(\(-\infty, \infty\)\\) as input (an angle 
-/// in radians) and returns a numeric value \\(y\\) that lies in the range 
+/// The function takes a number \\(x\\) in its domain \\(\(-\infty, \infty\)\\) as input (an angle
+/// in radians) and returns a numeric value \\(y\\) that lies in the range
 /// \\(\(-\infty, \infty\)\\). If the input value is too large an overflow error might occur.
 ///
 /// <details>
@@ -507,7 +491,7 @@ fn do_cosh(a: Float) -> Float
 /// \forall x \in \(-\infty, +\infty\),   \\; \sin{(x)} = y \in \[-1, 1\]
 /// \\]
 ///
-/// The function takes a number \\(x\\) in its domain \\(\(-\infty, \infty\)\\) (an angle in 
+/// The function takes a number \\(x\\) in its domain \\(\(-\infty, \infty\)\\) (an angle in
 /// radians) as input and returns a numeric value \\(y\\) that lies in the range \\(\[-1, 1\]\\).
 ///
 /// <details>
@@ -753,14 +737,10 @@ fn do_exponential(a: Float) -> Float
 ///     </a>
 /// </div>
 ///
-pub fn natural_logarithm(x: Float) -> Result(Float, String) {
+pub fn natural_logarithm(x: Float) -> Result(Float, Nil) {
   case x >. 0.0 {
-    True ->
-      do_natural_logarithm(x)
-      |> Ok
-    False ->
-      "Invalid input argument: x <= 0. Valid input is x > 0."
-      |> Error
+    True -> Ok(do_natural_logarithm(x))
+    False -> Error(Nil)
   }
 }
 
@@ -780,7 +760,7 @@ fn do_natural_logarithm(a: Float) -> Float
 /// \forall x \in \(0, \infty\) \textnormal{ and } b > 1,  \\; \log_{b}{(x)} = y \in \(-\infty, +\infty\)
 /// \\]
 ///
-/// The function takes a number \\(x\\) in its domain \\(\(0, \infty\)\\) and a base \\(b > 1\\) 
+/// The function takes a number \\(x\\) in its domain \\(\(0, \infty\)\\) and a base \\(b > 1\\)
 /// as input and returns a numeric value \\(y\\) that lies in the range \\(\(-\infty, \infty\)\\).
 /// If the input value is outside the domain of the function an error is returned.
 ///
@@ -810,30 +790,19 @@ fn do_natural_logarithm(a: Float) -> Float
 ///     </a>
 /// </div>
 ///
-pub fn logarithm(x: Float, base: option.Option(Float)) -> Result(Float, String) {
-  case x >. 0.0 {
-    True ->
-      case base {
-        option.Some(a) ->
-          case a >. 0.0 && a != 1.0 {
-            True -> {
-              // Apply the "change of base formula"
-              let assert Ok(numerator) = logarithm_10(x)
-              let assert Ok(denominator) = logarithm_10(a)
-              numerator /. denominator
-              |> Ok
-            }
-            False ->
-              "Invalid input argument: base <= 0 or base == 1. Valid input is base > 0 and base != 1."
-              |> Error
-          }
-        _ ->
-          "Invalid input argument: base <= 0 or base == 1. Valid input is base > 0 and base != 1."
-          |> Error
+pub fn logarithm(x: Float, base: option.Option(Float)) -> Result(Float, Nil) {
+  case x >. 0.0, base {
+    True, option.Some(a) ->
+      case a >. 0.0 && a != 1.0 {
+        False -> Error(Nil)
+        True -> {
+          // Apply the "change of base formula"
+          let assert Ok(numerator) = logarithm_10(x)
+          let assert Ok(denominator) = logarithm_10(a)
+          Ok(numerator /. denominator)
+        }
       }
-    _ ->
-      "Invalid input argument: x <= 0. Valid input is x > 0."
-      |> Error
+    _, _ -> Error(Nil)
   }
 }
 
@@ -849,7 +818,7 @@ pub fn logarithm(x: Float, base: option.Option(Float)) -> Result(Float, String)
 /// \forall x \in \(0, \infty),   \\; \log_{2}{(x)} = y \in \(-\infty, +\infty\)
 /// \\]
 ///
-/// The function takes a number \\(x\\) in its domain \\(\(0, \infty\)\\) as input and returns a 
+/// The function takes a number \\(x\\) in its domain \\(\(0, \infty\)\\) as input and returns a
 /// numeric value \\(y\\) that lies in the range \\(\(-\infty, \infty\)\\).
 /// If the input value is outside the domain of the function an error is returned.
 ///
@@ -877,14 +846,10 @@ pub fn logarithm(x: Float, base: option.Option(Float)) -> Result(Float, String)
 ///     </a>
 /// </div>
 ///
-pub fn logarithm_2(x: Float) -> Result(Float, String) {
+pub fn logarithm_2(x: Float) -> Result(Float, Nil) {
   case x >. 0.0 {
-    True ->
-      do_logarithm_2(x)
-      |> Ok
-    False ->
-      "Invalid input argument: x <= 0. Valid input is x > 0."
-      |> Error
+    True -> Ok(do_logarithm_2(x))
+    False -> Error(Nil)
   }
 }
 
@@ -904,7 +869,7 @@ fn do_logarithm_2(a: Float) -> Float
 /// \forall x \in \(0, \infty),   \\; \log_{10}{(x)} = y \in \(-\infty, +\infty\)
 /// \\]
 ///
-/// The function takes a number \\(x\\) in its domain \\(\(0, \infty\)\\) as input and returns a 
+/// The function takes a number \\(x\\) in its domain \\(\(0, \infty\)\\) as input and returns a
 /// numeric value \\(y\\) that lies in the range \\(\(-\infty, \infty\)\\).
 /// If the input value is outside the domain of the function an error is returned.
 ///
@@ -932,14 +897,10 @@ fn do_logarithm_2(a: Float) -> Float
 ///     </a>
 /// </div>
 ///
-pub fn logarithm_10(x: Float) -> Result(Float, String) {
+pub fn logarithm_10(x: Float) -> Result(Float, Nil) {
   case x >. 0.0 {
-    True ->
-      do_logarithm_10(x)
-      |> Ok
-    False ->
-      "Invalid input argument: x <= 0. Valid input is x > 0."
-      |> Error
+    True -> Ok(do_logarithm_10(x))
+    False -> Error(Nil)
   }
 }
 
@@ -993,7 +954,7 @@ fn do_logarithm_10(a: Float) -> Float
 ///     </a>
 /// </div>
 ///
-pub fn power(x: Float, y: Float) -> Result(Float, String) {
+pub fn power(x: Float, y: Float) -> Result(Float, Nil) {
   let fractional: Bool = do_ceiling(y) -. y >. 0.0
   // In the following check:
   // 1. If the base (x) is negative and the exponent (y) is fractional
@@ -1002,9 +963,7 @@ pub fn power(x: Float, y: Float) -> Result(Float, String) {
   //    expression is equivalent to the exponent (y) divided by 0 and an
   //    error should be returned
   case { x <. 0.0 && fractional } || { x == 0.0 && y <. 0.0 } {
-    True ->
-      "Invalid input argument: x < 0 and y is fractional or x = 0 and y < 0."
-      |> Error
+    True -> Error(Nil)
     False ->
       do_power(x, y)
       |> Ok
@@ -1055,19 +1014,13 @@ fn do_ceiling(a: Float) -> Float
 ///     </a>
 /// </div>
 ///
-pub fn square_root(x: Float) -> Result(Float, String) {
+pub fn square_root(x: Float) -> Result(Float, Nil) {
   // In the following check:
-  // 1. If x is negative then return an error as it will otherwise be an 
+  // 1. If x is negative then return an error as it will otherwise be an
   // imaginary number
   case x <. 0.0 {
-    True ->
-      "Invalid input argument: x < 0."
-      |> Error
-    False -> {
-      let assert Ok(result) = power(x, 1.0 /. 2.0)
-      result
-      |> Ok
-    }
+    True -> Error(Nil)
+    False -> power(x, 1.0 /. 2.0)
   }
 }
 
@@ -1107,19 +1060,13 @@ pub fn square_root(x: Float) -> Result(Float, String) {
 ///     </a>
 /// </div>
 ///
-pub fn cube_root(x: Float) -> Result(Float, String) {
+pub fn cube_root(x: Float) -> Result(Float, Nil) {
   // In the following check:
-  // 1. If x is negative then return an error as it will otherwise be an 
+  // 1. If x is negative then return an error as it will otherwise be an
   // imaginary number
   case x <. 0.0 {
-    True ->
-      "Invalid input argument: x < 0."
-      |> Error
-    False -> {
-      let assert Ok(result) = power(x, 1.0 /. 3.0)
-      result
-      |> Ok
-    }
+    True -> Error(Nil)
+    False -> power(x, 1.0 /. 3.0)
   }
 }
 
@@ -1162,25 +1109,13 @@ pub fn cube_root(x: Float) -> Result(Float, String) {
 ///     </a>
 /// </div>
 ///
-pub fn nth_root(x: Float, n: Int) -> Result(Float, String) {
+pub fn nth_root(x: Float, n: Int) -> Result(Float, Nil) {
   // In the following check:
-  // 1. If x is negative then return an error as it will otherwise be an 
+  // 1. If x is negative then return an error as it will otherwise be an
   // imaginary number
-  case x <. 0.0 {
-    True ->
-      "Invalid input argument: x < 0. Valid input is x > 0"
-      |> Error
-    False ->
-      case n >= 1 {
-        True -> {
-          let assert Ok(result) = power(x, 1.0 /. int.to_float(n))
-          result
-          |> Ok
-        }
-        False ->
-          "Invalid input argument: n < 1. Valid input is n >= 2."
-          |> Error
-      }
+  case x >=. 0.0 && n >= 1 {
+    True -> power(x, 1.0 /. int.to_float(n))
+    False -> Error(Nil)
   }
 }
 
diff --git a/src/gleam_community/maths/metrics.gleam b/src/gleam_community/maths/metrics.gleam
index f4862eb..9047b02 100644
--- a/src/gleam_community/maths/metrics.gleam
+++ b/src/gleam_community/maths/metrics.gleam
@@ -20,18 +20,18 @@
 ////<style>
 ////    .katex { font-size: 1.1em; }
 ////</style>
-//// 
+////
 //// ---
-//// 
-//// Metrics: A module offering functions for calculating distances and other 
+////
+//// Metrics: A module offering functions for calculating distances and other
 //// types of metrics.
-//// 
+////
 //// Disclaimer: In this module, the terms "distance" and "metric" are used in
 //// a broad and practical sense. That is, they are used to denote any difference
-//// or discrepancy between two inputs. Consequently, they may not align with their 
+//// or discrepancy between two inputs. Consequently, they may not align with their
 //// precise mathematical definitions (in particular, some "distance" functions in
 //// this module do not satisfy the triangle inequality).
-//// 
+////
 //// * **Distance measures**
 ////   * [`norm`](#norm)
 ////   * [`manhattan_distance`](#manhattan_distance)
@@ -51,7 +51,7 @@
 ////   * [`median`](#median)
 ////   * [`variance`](#variance)
 ////   * [`standard_deviation`](#standard_deviation)
-//// 
+////
 
 import gleam/bool
 import gleam/float
@@ -59,6 +59,7 @@ import gleam/int
 import gleam/list
 import gleam/option
 import gleam/pair
+import gleam/result
 import gleam/set
 import gleam_community/maths/arithmetics
 import gleam_community/maths/conversion
@@ -72,21 +73,15 @@ fn validate_lists(
   xarr: List(Float),
   yarr: List(Float),
   weights: option.Option(List(Float)),
-) -> Result(Bool, String) {
+) -> Result(Bool, Nil) {
   case xarr, yarr {
-    [], _ ->
-      "Invalid input argument: The list xarr is empty."
-      |> Error
-    _, [] ->
-      "Invalid input argument: The list yarr is empty."
-      |> Error
+    [], _ -> Error(Nil)
+    _, [] -> Error(Nil)
     _, _ -> {
       let xarr_length: Int = list.length(xarr)
       let yarr_length: Int = list.length(yarr)
       case xarr_length == yarr_length, weights {
-        False, _ ->
-          "Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)."
-          |> Error
+        False, _ -> Error(Nil)
         True, option.None -> {
           True
           |> Ok
@@ -97,9 +92,7 @@ fn validate_lists(
             True -> {
               validate_weights(warr)
             }
-            False ->
-              "Invalid input argument: length(weights) != length(xarr) and length(weights) != length(yarr). Valid input is when length(weights) == length(xarr) == length(yarr)."
-              |> Error
+            False -> Error(Nil)
           }
         }
       }
@@ -107,16 +100,12 @@ fn validate_lists(
   }
 }
 
-fn validate_weights(warr: List(Float)) -> Result(Bool, String) {
+fn validate_weights(warr: List(Float)) -> Result(Bool, Nil) {
   // Check that all the given weights are positive
   let assert Ok(minimum) = piecewise.list_minimum(warr, float.compare)
   case minimum >=. 0.0 {
-    False ->
-      "Invalid input argument: One or more weights are negative. Valid input is when all weights are >= 0."
-      |> Error
-    True ->
-      True
-      |> Ok
+    False -> Error(Nil)
+    True -> Ok(True)
   }
 }
 
@@ -132,7 +121,7 @@ fn validate_weights(warr: List(Float)) -> Result(Bool, String) {
 /// \left( \sum_{i=1}^n w_{i} \left|x_{i}\right|^{p} \right)^{\frac{1}{p}}
 /// \\]
 ///
-/// In the formula, \\(n\\) is the length of the list and \\(x_i\\) is the value in 
+/// In the formula, \\(n\\) is the length of the list and \\(x_i\\) is the value in
 /// the input list indexed by \\(i\\), while \\(w_i \in \mathbb{R}_{+}\\) is
 /// a corresponding positive weight (\\(w_i = 1.0\\;\forall i=1...n\\) by default).
 ///
@@ -147,14 +136,14 @@ fn validate_weights(warr: List(Float)) -> Result(Bool, String) {
 ///
 ///     pub fn example() {
 ///       let assert Ok(tol) = elementary.power(-10.0, -6.0)
-///     
+///
 ///       let assert Ok(result) =
 ///         [1.0, 1.0, 1.0]
 ///         |> metrics.norm(1.0, option.None)
 ///       result
 ///       |> predicates.is_close(3.0, 0.0, tol)
 ///       |> should.be_true()
-///     
+///
 ///       let assert Ok(result) =
 ///         [1.0, 1.0, 1.0]
 ///         |> metrics.norm(-1.0, option.None)
@@ -174,7 +163,7 @@ pub fn norm(
   arr: List(Float),
   p: Float,
   weights: option.Option(List(Float)),
-) -> Result(Float, String) {
+) -> Result(Float, Nil) {
   case arr, weights {
     [], _ ->
       0.0
@@ -224,10 +213,7 @@ pub fn norm(
               |> Error
           }
         }
-        False -> {
-          "Invalid input argument: length(weights) != length(arr). Valid input is when length(weights) == length(arr)."
-          |> Error
-        }
+        False -> Error(Nil)
       }
     }
   }
@@ -239,16 +225,16 @@ pub fn norm(
 ///     </a>
 /// </div>
 ///
-/// Calculate the (weighted) Manhattan distance between two lists (representing 
+/// Calculate the (weighted) Manhattan distance between two lists (representing
 /// vectors):
 ///
 /// \\[
 /// \sum_{i=1}^n w_{i} \left|x_i - y_i \right|
 /// \\]
 ///
-/// In the formula, \\(n\\) is the length of the two lists and \\(x_i, y_i\\) are the 
+/// In the formula, \\(n\\) is the length of the two lists and \\(x_i, y_i\\) are the
 /// values in the respective input lists indexed by \\(i\\), while the
-/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights 
+/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights
 /// (\\(w_i = 1.0\\;\forall i=1...n\\) by default).
 ///
 /// <details>
@@ -266,11 +252,11 @@ pub fn norm(
 ///       // Empty lists returns an error
 ///       metrics.manhattan_distance([], [], option.None)
 ///       |> should.be_error()
-///     
+///
 ///       // Differing lengths returns error
 ///       metrics.manhattan_distance([], [1.0], option.None)
 ///       |> should.be_error()
-///     
+///
 ///       let assert Ok(result) =
 ///         metrics.manhattan_distance([0.0, 0.0], [1.0, 2.0], option.None)
 ///       result
@@ -289,7 +275,7 @@ pub fn manhattan_distance(
   xarr: List(Float),
   yarr: List(Float),
   weights: option.Option(List(Float)),
-) -> Result(Float, String) {
+) -> Result(Float, Nil) {
   minkowski_distance(xarr, yarr, 1.0, weights)
 }
 
@@ -306,12 +292,12 @@ pub fn manhattan_distance(
 /// \left( \sum_{i=1}^n w_{i} \left|x_i - y_i \right|^{p} \right)^{\frac{1}{p}}
 /// \\]
 ///
-/// In the formula, \\(p >= 1\\) is the order, \\(n\\) is the length of the two lists 
+/// In the formula, \\(p >= 1\\) is the order, \\(n\\) is the length of the two lists
 /// and \\(x_i, y_i\\) are the values in the respective input lists indexed by \\(i\\).
-/// The \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights 
+/// The \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights
 /// (\\(w_i = 1.0\\;\forall i=1...n\\) by default).
 ///
-/// The Minkowski distance is a generalization of both the Euclidean distance 
+/// The Minkowski distance is a generalization of both the Euclidean distance
 /// (\\(p=2\\)) and the Manhattan distance (\\(p = 1\\)).
 ///
 /// <details>
@@ -325,11 +311,11 @@ pub fn manhattan_distance(
 ///
 ///     pub fn example() {
 ///       let assert Ok(tol) = elementary.power(-10.0, -6.0)
-///     
+///
 ///       // Empty lists returns an error
 ///       metrics.minkowski_distance([], [], 1.0, option.None)
 ///       |> should.be_error()
-///     
+///
 ///       // Differing lengths returns error
 ///       metrics.minkowski_distance([], [1.0], 1.0, option.None)
 ///       |> should.be_error()
@@ -337,7 +323,7 @@ pub fn manhattan_distance(
 ///       // Test order < 1
 ///       metrics.minkowski_distance([0.0, 0.0], [0.0, 0.0], -1.0, option.None)
 ///       |> should.be_error()
-///     
+///
 ///       let assert Ok(result) =
 ///         metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0, option.None)
 ///       result
@@ -357,28 +343,18 @@ pub fn minkowski_distance(
   yarr: List(Float),
   p: Float,
   weights: option.Option(List(Float)),
-) -> Result(Float, String) {
-  case validate_lists(xarr, yarr, weights) {
-    Error(msg) ->
-      msg
-      |> Error
-    Ok(_) -> {
-      case p <. 1.0 {
-        True ->
-          "Invalid input argument: p < 1. Valid input is p >= 1."
-          |> Error
-        False -> {
-          let differences: List(Float) =
-            list.zip(xarr, yarr)
-            |> list.map(fn(tuple: #(Float, Float)) -> Float {
-              pair.first(tuple) -. pair.second(tuple)
-            })
+) -> Result(Float, Nil) {
+  use _ <- result.try(validate_lists(xarr, yarr, weights))
+  case p <. 1.0 {
+    True -> Error(Nil)
+    False -> {
+      let differences: List(Float) =
+        list.zip(xarr, yarr)
+        |> list.map(fn(tuple: #(Float, Float)) -> Float {
+          pair.first(tuple) -. pair.second(tuple)
+        })
 
-          let assert Ok(result) = norm(differences, p, weights)
-          result
-          |> Ok
-        }
-      }
+      norm(differences, p, weights)
     }
   }
 }
@@ -389,7 +365,7 @@ pub fn minkowski_distance(
 ///     </a>
 /// </div>
 ///
-/// Calculate the (weighted) Euclidean distance between two lists (representing 
+/// Calculate the (weighted) Euclidean distance between two lists (representing
 /// vectors):
 ///
 /// \\[
@@ -398,7 +374,7 @@ pub fn minkowski_distance(
 ///
 /// In the formula, \\(n\\) is the length of the two lists and \\(x_i, y_i\\) are the
 /// values in the respective input lists indexed by \\(i\\), while the
-/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights 
+/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights
 /// (\\(w_i = 1.0\\;\forall i=1...n\\) by default).
 ///
 /// <details>
@@ -412,15 +388,15 @@ pub fn minkowski_distance(
 ///
 ///     pub fn example() {
 ///       let assert Ok(tol) = elementary.power(-10.0, -6.0)
-///     
+///
 ///       // Empty lists returns an error
 ///       metrics.euclidean_distance([], [], option.None)
 ///       |> should.be_error()
-///     
+///
 ///       // Differing lengths returns an error
 ///       metrics.euclidean_distance([], [1.0], option.None)
 ///       |> should.be_error()
-///     
+///
 ///       let assert Ok(result) =
 ///         metrics.euclidean_distance([0.0, 0.0], [1.0, 2.0], option.None)
 ///       result
@@ -439,7 +415,7 @@ pub fn euclidean_distance(
   xarr: List(Float),
   yarr: List(Float),
   weights: option.Option(List(Float)),
-) -> Result(Float, String) {
+) -> Result(Float, Nil) {
   minkowski_distance(xarr, yarr, 2.0, weights)
 }
 
@@ -455,7 +431,7 @@ pub fn euclidean_distance(
 /// \text{max}_{i=1}^n \left|x_i - y_i \right|
 /// \\]
 ///
-/// In the formula, \\(n\\) is the length of the two lists and \\(x_i, y_i\\) are the 
+/// In the formula, \\(n\\) is the length of the two lists and \\(x_i, y_i\\) are the
 /// values in the respective input lists indexed by \\(i\\).
 ///
 /// <details>
@@ -470,11 +446,11 @@ pub fn euclidean_distance(
 ///       // Empty lists returns an error
 ///       metrics.chebyshev_distance([], [])
 ///       |> should.be_error()
-///     
+///
 ///       // Differing lengths returns error
 ///       metrics.chebyshev_distance([], [1.0])
 ///       |> should.be_error()
-///     
+///
 ///       metrics.chebyshev_distance([-5.0, -10.0, -3.0], [-1.0, -12.0, -3.0])
 ///       |> should.equal(Ok(4.0))
 ///     }
@@ -489,7 +465,7 @@ pub fn euclidean_distance(
 pub fn chebyshev_distance(
   xarr: List(Float),
   yarr: List(Float),
-) -> Result(Float, String) {
+) -> Result(Float, Nil) {
   case validate_lists(xarr, yarr, option.None) {
     Error(msg) ->
       msg
@@ -545,11 +521,9 @@ pub fn chebyshev_distance(
 ///     </a>
 /// </div>
 ///
-pub fn mean(arr: List(Float)) -> Result(Float, String) {
+pub fn mean(arr: List(Float)) -> Result(Float, Nil) {
   case arr {
-    [] ->
-      "Invalid input argument: The list is empty."
-      |> Error
+    [] -> Error(Nil)
     _ ->
       arr
       |> arithmetics.float_sum(option.None)
@@ -632,14 +606,14 @@ fn do_median(
 /// </div>
 ///
 /// Calculate the sample variance of the elements in a list:
-/// 
+///
 /// \\[
 /// s^{2} = \frac{1}{n - d} \sum_{i=1}^{n}(x_i - \bar{x})
 /// \\]
 ///
-/// In the formula, \\(n\\) is the sample size (the length of the list) and \\(x_i\\) 
-/// is the sample point in the input list indexed by \\(i\\). 
-/// Furthermore, \\(\bar{x}\\) is the sample mean and \\(d\\) is the "Delta 
+/// In the formula, \\(n\\) is the sample size (the length of the list) and \\(x_i\\)
+/// is the sample point in the input list indexed by \\(i\\).
+/// Furthermore, \\(\bar{x}\\) is the sample mean and \\(d\\) is the "Delta
 /// Degrees of Freedom", and is by default set to \\(d = 0\\), which gives a biased
 /// estimate of the sample variance. Setting \\(d = 1\\) gives an unbiased estimate.
 ///
@@ -671,34 +645,27 @@ fn do_median(
 ///     </a>
 /// </div>
 ///
-pub fn variance(arr: List(Float), ddof: Int) -> Result(Float, String) {
-  case arr {
-    [] ->
-      "Invalid input argument: The list is empty."
-      |> Error
-    _ ->
-      case ddof < 0 {
-        True ->
-          "Invalid input argument: ddof < 0. Valid input is ddof >= 0."
-          |> Error
-        False -> {
-          let assert Ok(mean) = mean(arr)
-          arr
-          |> list.map(fn(a: Float) -> Float {
-            let assert Ok(result) = elementary.power(a -. mean, 2.0)
-            result
-          })
-          |> arithmetics.float_sum(option.None)
-          |> fn(a: Float) -> Float {
-            a
-            /. {
-              conversion.int_to_float(list.length(arr))
-              -. conversion.int_to_float(ddof)
-            }
-          }
-          |> Ok
+pub fn variance(arr: List(Float), ddof: Int) -> Result(Float, Nil) {
+  case arr, ddof {
+    [], _ -> Error(Nil)
+    _, _ if ddof < 0 -> Error(Nil)
+    _, _ -> {
+      let assert Ok(mean) = mean(arr)
+      arr
+      |> list.map(fn(a: Float) -> Float {
+        let assert Ok(result) = elementary.power(a -. mean, 2.0)
+        result
+      })
+      |> arithmetics.float_sum(option.None)
+      |> fn(a: Float) -> Float {
+        a
+        /. {
+          conversion.int_to_float(list.length(arr))
+          -. conversion.int_to_float(ddof)
         }
       }
+      |> Ok
+    }
   }
 }
 
@@ -713,11 +680,11 @@ pub fn variance(arr: List(Float), ddof: Int) -> Result(Float, String) {
 /// s = \left(\frac{1}{n - d} \sum_{i=1}^{n}(x_i - \bar{x})\right)^{\frac{1}{2}}
 /// \\]
 ///
-/// In the formula, \\(n\\) is the sample size (the length of the list) and \\(x_i\\) 
-/// is the sample point in the input list indexed by \\(i\\). 
-/// Furthermore, \\(\bar{x}\\) is the sample mean and \\(d\\) is the "Delta 
+/// In the formula, \\(n\\) is the sample size (the length of the list) and \\(x_i\\)
+/// is the sample point in the input list indexed by \\(i\\).
+/// Furthermore, \\(\bar{x}\\) is the sample mean and \\(d\\) is the "Delta
 /// Degrees of Freedom", and is by default set to \\(d = 0\\), which gives a biased
-/// estimate of the sample standard deviation. Setting \\(d = 1\\) gives an unbiased 
+/// estimate of the sample standard deviation. Setting \\(d = 1\\) gives an unbiased
 /// estimate.
 ///
 /// <details>
@@ -748,25 +715,18 @@ pub fn variance(arr: List(Float), ddof: Int) -> Result(Float, String) {
 ///     </a>
 /// </div>
 ///
-pub fn standard_deviation(arr: List(Float), ddof: Int) -> Result(Float, String) {
-  case arr {
-    [] ->
-      "Invalid input argument: The list is empty."
-      |> Error
-    _ ->
-      case ddof < 0 {
-        True ->
-          "Invalid input argument: ddof < 0. Valid input is ddof >= 0."
-          |> Error
-        False -> {
-          let assert Ok(variance) = variance(arr, ddof)
-          // The computed variance will always be positive
-          // So an error should never be returned
-          let assert Ok(stdev) = elementary.square_root(variance)
-          stdev
-          |> Ok
-        }
-      }
+pub fn standard_deviation(arr: List(Float), ddof: Int) -> Result(Float, Nil) {
+  case arr, ddof {
+    [], _ -> Error(Nil)
+    _, _ if ddof < 0 -> Error(Nil)
+    _, _ -> {
+      let assert Ok(variance) = variance(arr, ddof)
+      // The computed variance will always be positive
+      // So an error should never be returned
+      let assert Ok(stdev) = elementary.square_root(variance)
+      stdev
+      |> Ok
+    }
   }
 }
 
@@ -776,24 +736,24 @@ pub fn standard_deviation(arr: List(Float), ddof: Int) -> Result(Float, String)
 ///     </a>
 /// </div>
 ///
-/// The Jaccard index measures similarity between two sets of elements. 
+/// The Jaccard index measures similarity between two sets of elements.
 /// Mathematically, the Jaccard index is defined as:
-/// 
+///
 /// \\[
 /// \frac{|X \cap Y|}{|X \cup Y|} \\; \in \\; \left[0, 1\right]
 /// \\]
-/// 
+///
 /// where:
 ///
 /// - \\(X\\) and \\(Y\\) are two sets being compared,
 /// - \\(|X \cap Y|\\) represents the size of the intersection of the two sets
 /// - \\(|X \cup Y|\\) denotes the size of the union of the two sets
-/// 
-/// The value of the Jaccard index ranges from 0 to 1, where 0 indicates that the 
-/// two sets share no elements and 1 indicates that the sets are identical. The 
+///
+/// The value of the Jaccard index ranges from 0 to 1, where 0 indicates that the
+/// two sets share no elements and 1 indicates that the sets are identical. The
 /// Jaccard index is a special case of the  [Tversky index](#tversky_index) (with
 /// \\(\alpha=\beta=1\\)).
-/// 
+///
 /// <details>
 ///     <summary>Example:</summary>
 ///
@@ -827,25 +787,25 @@ pub fn jaccard_index(xset: set.Set(a), yset: set.Set(a)) -> Float {
 ///     </a>
 /// </div>
 ///
-/// The Sørensen-Dice coefficient measures the similarity between two sets of 
+/// The Sørensen-Dice coefficient measures the similarity between two sets of
 /// elements. Mathematically, the coefficient is defined as:
-/// 
+///
 /// \\[
 /// \frac{2 |X \cap Y|}{|X| + |Y|} \\; \in \\; \left[0, 1\right]
 /// \\]
-/// 
+///
 /// where:
 /// - \\(X\\) and \\(Y\\) are two sets being compared
-/// - \\(|X \cap Y|\\) is the size of the intersection of the two sets (i.e., the 
+/// - \\(|X \cap Y|\\) is the size of the intersection of the two sets (i.e., the
 /// number of elements common to both sets)
 /// - \\(|X|\\) and \\(|Y|\\) are the sizes of the sets \\(X\\) and \\(Y\\), respectively
-/// 
+///
 /// The coefficient ranges from 0 to 1, where 0 indicates no similarity (the sets
 /// share no elements) and 1 indicates perfect similarity (the sets are identical).
-/// The higher the coefficient, the greater the similarity between the two sets. 
-/// The Sørensen-Dice coefficient is a special case of the 
+/// The higher the coefficient, the greater the similarity between the two sets.
+/// The Sørensen-Dice coefficient is a special case of the
 /// [Tversky index](#tversky_index) (with \\(\alpha=\beta=0.5\\)).
-/// 
+///
 /// <details>
 ///     <summary>Example:</summary>
 ///
@@ -878,31 +838,31 @@ pub fn sorensen_dice_coefficient(xset: set.Set(a), yset: set.Set(a)) -> Float {
 ///         <small>Spot a typo? Open an issue!</small>
 ///     </a>
 /// </div>
-/// 
-/// The Tversky index is a generalization of the Jaccard index and Sørensen-Dice 
-/// coefficient, which adds flexibility through two parameters, \\(\alpha\\) and 
-/// \\(\beta\\), allowing for asymmetric similarity measures between sets. The 
+///
+/// The Tversky index is a generalization of the Jaccard index and Sørensen-Dice
+/// coefficient, which adds flexibility through two parameters, \\(\alpha\\) and
+/// \\(\beta\\), allowing for asymmetric similarity measures between sets. The
 /// Tversky index is defined as:
-/// 
+///
 /// \\[
 /// \frac{|X \cap Y|}{|X \cap Y| + \alpha|X - Y| + \beta|Y - X|}
 /// \\]
-/// 
+///
 /// where:
-/// 
+///
 /// - \\(X\\) and \\(Y\\) are the sets being compared
-/// - \\(|X - Y|\\) and \\(|Y - X|\\) are the sizes of the relative complements of 
+/// - \\(|X - Y|\\) and \\(|Y - X|\\) are the sizes of the relative complements of
 /// \\(Y\\) in \\(X\\) and \\(X\\) in \\(Y\\), respectively,
 /// - \\(\alpha\\) and \\(\beta\\) are parameters that weigh the relative importance
 /// of the elements unique to \\(X\\) and \\(Y\\)
-/// 
+///
 /// The Tversky index reduces to the Jaccard index when \\(\alpha = \beta = 1\\) and
 /// to the Sørensen-Dice coefficient when \\(\alpha = \beta = 0.5\\). In general, the
 /// Tversky index can take on any non-negative value, including 0. The index equals
-/// 0 when there is no intersection between the two sets, indicating no similarity. 
-/// However, unlike similarity measures bounded strictly between 0 and 1, the 
+/// 0 when there is no intersection between the two sets, indicating no similarity.
+/// However, unlike similarity measures bounded strictly between 0 and 1, the
 /// Tversky index does not have a strict upper limit of 1 when \\(\alpha \neq \beta\\).
-///  
+///
 /// <details>
 ///     <summary>Example:</summary>
 ///
@@ -931,7 +891,7 @@ pub fn tversky_index(
   yset: set.Set(a),
   alpha: Float,
   beta: Float,
-) -> Result(Float, String) {
+) -> Result(Float, Nil) {
   case alpha >=. 0.0, beta >=. 0.0 {
     True, True -> {
       let intersection: Float =
@@ -950,18 +910,7 @@ pub fn tversky_index(
       /. { intersection +. alpha *. difference1 +. beta *. difference2 }
       |> Ok
     }
-    False, True -> {
-      "Invalid input argument: alpha < 0. Valid input is alpha >= 0."
-      |> Error
-    }
-    True, False -> {
-      "Invalid input argument: beta < 0. Valid input is beta >= 0."
-      |> Error
-    }
-    _, _ -> {
-      "Invalid input argument: alpha < 0 and beta < 0. Valid input is alpha >= 0 and beta >= 0."
-      |> Error
-    }
+    _, _ -> Error(Nil)
   }
 }
 
@@ -970,10 +919,10 @@ pub fn tversky_index(
 ///         <small>Spot a typo? Open an issue!</small>
 ///     </a>
 /// </div>
-/// 
+///
 /// The Overlap coefficient, also known as the Szymkiewicz–Simpson coefficient, is
-/// a measure of similarity between two sets that focuses on the size of the 
-/// intersection relative to the smaller of the two sets. It is defined 
+/// a measure of similarity between two sets that focuses on the size of the
+/// intersection relative to the smaller of the two sets. It is defined
 /// mathematically as:
 ///
 /// \\[
@@ -986,10 +935,10 @@ pub fn tversky_index(
 /// - \\(|X \cap Y|\\) is the size of the intersection of the sets
 /// - \\(\min(|X|, |Y|)\\) is the size of the smaller set among \\(X\\) and \\(Y\\)
 ///
-/// The coefficient ranges from 0 to 1, where 0 indicates no overlap and 1 
-/// indicates that the smaller set is a suyset of the larger set. This 
+/// The coefficient ranges from 0 to 1, where 0 indicates no overlap and 1
+/// indicates that the smaller set is a suyset of the larger set. This
 /// measure is especially useful in situations where the similarity in terms
-/// of the proportion of overlap is more relevant than the difference in sizes 
+/// of the proportion of overlap is more relevant than the difference in sizes
 /// between the two sets.
 ///
 /// <details>
@@ -1031,27 +980,27 @@ pub fn overlap_coefficient(xset: set.Set(a), yset: set.Set(a)) -> Float {
 ///         <small>Spot a typo? Open an issue!</small>
 ///     </a>
 /// </div>
-/// 
+///
 /// Calculate the (weighted) cosine similarity between two lists (representing
 /// vectors):
 ///
 /// \\[
 /// \frac{\sum_{i=1}^n w_{i} \cdot x_i \cdot y_i}
 /// {\left(\sum_{i=1}^n w_{i} \cdot x_i^2\right)^{\frac{1}{2}}
-/// \cdot 
-/// \left(\sum_{i=1}^n w_{i} \cdot y_i^2\right)^{\frac{1}{2}}} 
+/// \cdot
+/// \left(\sum_{i=1}^n w_{i} \cdot y_i^2\right)^{\frac{1}{2}}}
 /// \\; \in \\; \left[-1, 1\right]
 /// \\]
 ///
 /// In the formula, \\(n\\) is the length of the two lists and \\(x_i\\), \\(y_i\\) are
 /// the values in the respective input lists indexed by \\(i\\), while the
-/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights 
-/// (\\(w_i = 1.0\\;\forall i=1...n\\) by default). 
-/// 
-/// The cosine similarity provides a value between -1 and 1, where 1 means the 
-/// vectors are in the same direction, -1 means they are in exactly opposite 
-/// directions, and 0 indicates orthogonality. 
-/// 
+/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights
+/// (\\(w_i = 1.0\\;\forall i=1...n\\) by default).
+///
+/// The cosine similarity provides a value between -1 and 1, where 1 means the
+/// vectors are in the same direction, -1 means they are in exactly opposite
+/// directions, and 0 indicates orthogonality.
+///
 /// <details>
 ///     <summary>Example:</summary>
 ///
@@ -1063,11 +1012,11 @@ pub fn overlap_coefficient(xset: set.Set(a), yset: set.Set(a)) -> Float {
 ///       // Two orthogonal vectors
 ///       metrics.cosine_similarity([-1.0, 1.0, 0.0], [1.0, 1.0, -1.0], option.None)
 ///       |> should.equal(Ok(0.0))
-///     
+///
 ///       // Two identical (parallel) vectors
 ///       metrics.cosine_similarity([1.0, 2.0, 3.0], [1.0, 2.0, 3.0], option.None)
 ///       |> should.equal(Ok(1.0))
-///     
+///
 ///       // Two parallel, but oppositely oriented vectors
 ///       metrics.cosine_similarity([-1.0, -2.0, -3.0], [1.0, 2.0, 3.0], option.None)
 ///       |> should.equal(Ok(-1.0))
@@ -1084,7 +1033,7 @@ pub fn cosine_similarity(
   xarr: List(Float),
   yarr: List(Float),
   weights: option.Option(List(Float)),
-) -> Result(Float, String) {
+) -> Result(Float, Nil) {
   case validate_lists(xarr, yarr, weights) {
     Error(msg) ->
       msg
@@ -1135,7 +1084,7 @@ pub fn cosine_similarity(
 ///         <small>Spot a typo? Open an issue!</small>
 ///     </a>
 /// </div>
-/// 
+///
 /// Calculate the (weighted) Canberra distance between two lists:
 ///
 /// \\[
@@ -1143,10 +1092,10 @@ pub fn cosine_similarity(
 /// {\left| x_i \right| + \left| y_i \right|}
 /// \\]
 ///
-/// In the formula, \\(n\\) is the length of the two lists, and \\(x_i, y_i\\) are the 
-/// values in the respective input lists indexed by \\(i\\), while the 
-/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights 
-/// (\\(w_i = 1.0\\;\forall i=1...n\\) by default). 
+/// In the formula, \\(n\\) is the length of the two lists, and \\(x_i, y_i\\) are the
+/// values in the respective input lists indexed by \\(i\\), while the
+/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights
+/// (\\(w_i = 1.0\\;\forall i=1...n\\) by default).
 ///
 /// <details>
 ///     <summary>Example:</summary>
@@ -1159,15 +1108,15 @@ pub fn cosine_similarity(
 ///       // Empty lists returns an error
 ///       metrics.canberra_distance([], [], option.None)
 ///       |> should.be_error()
-///     
+///
 ///       // Different sized lists returns an error
 ///       metrics.canberra_distance([1.0, 2.0], [1.0, 2.0, 3.0, 4.0], option.None)
 ///       |> should.be_error()
-///     
+///
 ///       // Valid inputs
 ///       metrics.canberra_distance([1.0, 2.0], [-2.0, -1.0], option.None)
 ///       |> should.equal(Ok(2.0))
-///     
+///
 ///       metrics.canberra_distance([1.0, 0.0], [0.0, 2.0], option.Some([1.0, 0.5]))
 ///     }
 /// </details>
@@ -1182,7 +1131,7 @@ pub fn canberra_distance(
   xarr: List(Float),
   yarr: List(Float),
   weights: option.Option(List(Float)),
-) -> Result(Float, String) {
+) -> Result(Float, Nil) {
   case validate_lists(xarr, yarr, weights) {
     Error(msg) ->
       msg
@@ -1223,7 +1172,7 @@ fn canberra_distance_helper(tuple: #(Float, Float)) -> Float {
 ///         <small>Spot a typo? Open an issue!</small>
 ///     </a>
 /// </div>
-/// 
+///
 /// Calculate the (weighted) Bray-Curtis distance between two lists:
 ///
 /// \\[
@@ -1231,11 +1180,11 @@ fn canberra_distance_helper(tuple: #(Float, Float)) -> Float {
 /// {\sum_{i=1}^n w_{i}\left| x_i + y_i \right|}
 /// \\]
 ///
-/// In the formula, \\(n\\) is the length of the two lists, and \\(x_i, y_i\\) are the values 
-/// in the respective input lists indexed by \\(i\\), while the 
-/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights 
+/// In the formula, \\(n\\) is the length of the two lists, and \\(x_i, y_i\\) are the values
+/// in the respective input lists indexed by \\(i\\), while the
+/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights
 /// (\\(w_i = 1.0\\;\forall i=1...n\\) by default).
-/// 
+///
 /// The Bray-Curtis distance is in the range \\([0, 1]\\) if all entries \\(x_i, y_i\\) are
 /// positive.
 ///
@@ -1250,15 +1199,15 @@ fn canberra_distance_helper(tuple: #(Float, Float)) -> Float {
 ///       // Empty lists returns an error
 ///       metrics.braycurtis_distance([], [], option.None)
 ///       |> should.be_error()
-///     
+///
 ///       // Different sized lists returns an error
 ///       metrics.braycurtis_distance([1.0, 2.0], [1.0, 2.0, 3.0, 4.0], option.None)
 ///       |> should.be_error()
-///     
+///
 ///       // Valid inputs
 ///       metrics.braycurtis_distance([1.0, 0.0], [0.0, 2.0], option.None)
 ///       |> should.equal(Ok(1.0))
-///     
+///
 ///       metrics.braycurtis_distance([1.0, 2.0], [3.0, 4.0], option.Some([0.5, 1.0]))
 ///       |> should.equal(Ok(0.375))
 ///     }
@@ -1275,7 +1224,7 @@ pub fn braycurtis_distance(
   xarr: List(Float),
   yarr: List(Float),
   weights: option.Option(List(Float)),
-) -> Result(Float, String) {
+) -> Result(Float, Nil) {
   case validate_lists(xarr, yarr, weights) {
     Error(msg) ->
       msg
diff --git a/src/gleam_community/maths/piecewise.gleam b/src/gleam_community/maths/piecewise.gleam
index 36a18dc..6931f8a 100644
--- a/src/gleam_community/maths/piecewise.gleam
+++ b/src/gleam_community/maths/piecewise.gleam
@@ -69,7 +69,7 @@ import gleam_community/maths/elementary
 /// The ceiling function rounds a given input value \\(x \in \mathbb{R}\\) to the nearest integer
 /// value (at the specified digit) that is larger than or equal to the input \\(x\\).
 ///
-/// Note: The ceiling function is used as an alias for the rounding function [`round`](#round) 
+/// Note: The ceiling function is used as an alias for the rounding function [`round`](#round)
 /// with rounding mode `RoundUp`.
 ///
 /// <details>
@@ -124,10 +124,10 @@ pub fn ceiling(x: Float, digits: option.Option(Int)) -> Float {
 ///     </a>
 /// </div>
 ///
-/// The floor function rounds input \\(x \in \mathbb{R}\\) to the nearest integer value (at the 
+/// The floor function rounds input \\(x \in \mathbb{R}\\) to the nearest integer value (at the
 /// specified digit) that is less than or equal to the input \\(x\\).
 ///
-/// Note: The floor function is used as an alias for the rounding function [`round`](#round) 
+/// Note: The floor function is used as an alias for the rounding function [`round`](#round)
 /// with rounding mode `RoundDown`.
 ///
 /// <details>
@@ -139,7 +139,7 @@ pub fn ceiling(x: Float, digits: option.Option(Int)) -> Float {
 ///   - \\(12.06\\) for 2 digits after the decimal point (`digits = 2`)
 ///   - \\(12.065\\) for 3 digits after the decimal point (`digits = 3`)
 ///
-///   It is also possible to specify a negative number of digits. In that case, the negative 
+///   It is also possible to specify a negative number of digits. In that case, the negative
 ///   number refers to the digits before the decimal point.
 ///   - \\(10.0\\) for 1 digit before the decimal point (`digits = -1`)
 ///   - \\(0.0\\) for 2 digits before the decimal point (`digits = -2`)
@@ -182,11 +182,11 @@ pub fn floor(x: Float, digits: option.Option(Int)) -> Float {
 ///     </a>
 /// </div>
 ///
-/// The truncate function rounds a given input \\(x \in \mathbb{R}\\) to the nearest integer 
-/// value (at the specified digit) that is less than or equal to the absolute value of the 
+/// The truncate function rounds a given input \\(x \in \mathbb{R}\\) to the nearest integer
+/// value (at the specified digit) that is less than or equal to the absolute value of the
 /// input \\(x\\).
 ///
-/// Note: The truncate function is used as an alias for the rounding function [`round`](#round) 
+/// Note: The truncate function is used as an alias for the rounding function [`round`](#round)
 /// with rounding mode `RoundToZero`.
 ///
 /// <details>
@@ -198,7 +198,7 @@ pub fn floor(x: Float, digits: option.Option(Int)) -> Float {
 ///   - \\(12.06\\) for 2 digits after the decimal point (`digits = 2`)
 ///   - \\(12.065\\) for 3 digits after the decimal point (`digits = 3`)
 ///
-///   It is also possible to specify a negative number of digits. In that case, the negative 
+///   It is also possible to specify a negative number of digits. In that case, the negative
 ///   number refers to the digits before the decimal point.
 ///   - \\(10.0\\) for 1 digit before the decimal point (`digits = -1`)
 ///   - \\(0.0\\) for 2 digits before the decimal point (`digits = -2`)
@@ -241,18 +241,18 @@ pub fn truncate(x: Float, digits: option.Option(Int)) -> Float {
 ///     </a>
 /// </div>
 ///
-/// The function rounds a float to a specific number of digits (after the decimal place or before 
+/// The function rounds a float to a specific number of digits (after the decimal place or before
 /// if negative) using a specified rounding mode.
 ///
 /// Valid rounding modes include:
-/// - `RoundNearest` (default): The input \\(x\\) is rounded to the nearest integer value (at the 
-///   specified digit) with ties (fractional values of 0.5) being rounded to the nearest even 
+/// - `RoundNearest` (default): The input \\(x\\) is rounded to the nearest integer value (at the
+///   specified digit) with ties (fractional values of 0.5) being rounded to the nearest even
 ///   integer.
 /// - `RoundTiesAway`: The input \\(x\\) is rounded to the nearest integer value (at the
-///    specified digit) with ties (fractional values of 0.5) being rounded away from zero (C/C++ 
+///    specified digit) with ties (fractional values of 0.5) being rounded away from zero (C/C++
 ///   rounding behavior).
-/// - `RoundTiesUp`: The input \\(x\\) is rounded to the nearest integer value (at the specified 
-///   digit) with ties (fractional values of 0.5) being rounded towards \\(+\infty\\) 
+/// - `RoundTiesUp`: The input \\(x\\) is rounded to the nearest integer value (at the specified
+///   digit) with ties (fractional values of 0.5) being rounded towards \\(+\infty\\)
 ///   (Java/JavaScript rounding behaviour).
 /// - `RoundToZero`: The input \\(x\\) is rounded to the nearest integer value (at the specified
 ///    digit) that is less than or equal to the absolute value of the input \\(x\\). An alias for
@@ -260,8 +260,8 @@ pub fn truncate(x: Float, digits: option.Option(Int)) -> Float {
 /// - `RoundDown`: The input \\(x\\) is rounded to the nearest integer value (at the specified
 ///   digit) that is less than or equal to the input \\(x\\). An alias for this rounding mode is
 ///    [`floor`](#floor).
-/// - `RoundUp`: The input \\(x\\) is rounded to the nearest integer value (at the specified 
-///   digit) that is larger than or equal to the input \\(x\\). An alias for this rounding mode 
+/// - `RoundUp`: The input \\(x\\) is rounded to the nearest integer value (at the specified
+///   digit) that is larger than or equal to the input \\(x\\). An alias for this rounding mode
 ///   is [`ceiling`](#ceiling).
 ///
 /// <details>
@@ -273,7 +273,7 @@ pub fn truncate(x: Float, digits: option.Option(Int)) -> Float {
 ///   - \\(12.07\\) for 2 digits after the decimal point (`digits = 2`)
 ///   - \\(12.065\\) for 3 digits after the decimal point (`digits = 3`)
 ///
-///   It is also possible to specify a negative number of digits. In that case, the negative 
+///   It is also possible to specify a negative number of digits. In that case, the negative
 ///   number refers to the digits before the decimal point.
 ///   - \\(10.0\\) for 1 digit before the decimal point (`digits = -1`)
 ///   - \\(0.0\\) for 2 digits before the decimal point (`digits = -2`)
@@ -285,7 +285,7 @@ pub fn truncate(x: Float, digits: option.Option(Int)) -> Float {
 ///   - \\(12.07\\) for 2 digits after the decimal point (`digits = 2`)
 ///   - \\(12.065\\) for 3 digits after the decimal point (`digits = 3`)
 ///
-///   It is also possible to specify a negative number of digits. In that case, the negative 
+///   It is also possible to specify a negative number of digits. In that case, the negative
 ///   number refers to the digits before the decimal point.
 ///   - \\(10.0\\) for 1 digit before the decimal point (`digits = -1`)
 ///   - \\(0.0\\) for 2 digits before the decimal point (`digits = -2`)
@@ -309,7 +309,7 @@ pub fn truncate(x: Float, digits: option.Option(Int)) -> Float {
 ///   - \\(12.06\\) for 2 digits after the decimal point (`digits = 2`)
 ///   - \\(12.065\\) for 3 digits after the decimal point (`digits = 3`)
 ///
-///   It is also possible to specify a negative number of digits. In that case, the negative 
+///   It is also possible to specify a negative number of digits. In that case, the negative
 ///   number refers to the digits before the decimal point.
 ///   - \\(10.0\\) for 1 digit before the decimal point (`digits = -1`)
 ///   - \\(0.0\\) for 2 digits before the decimal point (`digits = -2`)
@@ -321,7 +321,7 @@ pub fn truncate(x: Float, digits: option.Option(Int)) -> Float {
 ///   - \\(12.06\\) for 2 digits after the decimal point (`digits = 2`)
 ///   - \\(12.065\\) for 3 digits after the decimal point (`digits = 3`)
 ///
-///   It is also possible to specify a negative number of digits. In that case, the negative 
+///   It is also possible to specify a negative number of digits. In that case, the negative
 ///   number refers to the digits before the decimal point.
 ///   - \\(10.0\\) for 1 digit before the decimal point (`digits = -1`)
 ///   - \\(0.0\\) for 2 digits before the decimal point (`digits = -2`)
@@ -500,7 +500,7 @@ fn do_ceiling(a: Float) -> Float
 /// The absolute value:
 ///
 /// \\[
-///  \forall x \in \mathbb{R}, \\; |x|  \in \mathbb{R}_{+}. 
+///  \forall x \in \mathbb{R}, \\; |x|  \in \mathbb{R}_{+}.
 /// \\]
 ///
 /// The function takes an input \\(x\\) and returns a positive float value.
@@ -529,7 +529,7 @@ pub fn float_absolute_value(x: Float) -> Float {
 /// The absolute value:
 ///
 /// \\[
-///  \forall x \in \mathbb{Z}, \\; |x|  \in \mathbb{Z}_{+}. 
+///  \forall x \in \mathbb{Z}, \\; |x|  \in \mathbb{Z}_{+}.
 /// \\]
 ///
 /// The function takes an input \\(x\\) and returns a positive integer value.
@@ -709,7 +709,7 @@ fn do_int_sign(a: Int) -> Int
 ///     </a>
 /// </div>
 ///
-/// The function takes two arguments \\(x, y \in \mathbb{R}\\) and returns \\(x\\) 
+/// The function takes two arguments \\(x, y \in \mathbb{R}\\) and returns \\(x\\)
 /// such that it has the same sign as \\(y\\).
 ///
 /// <div style="text-align: right;">
@@ -735,7 +735,7 @@ pub fn float_copy_sign(x: Float, y: Float) -> Float {
 ///     </a>
 /// </div>
 ///
-/// The function takes two arguments \\(x, y \in \mathbb{Z}\\) and returns \\(x\\) 
+/// The function takes two arguments \\(x, y \in \mathbb{Z}\\) and returns \\(x\\)
 /// such that it has the same sign as \\(y\\).
 ///
 /// <div style="text-align: right;">
@@ -949,11 +949,9 @@ pub fn minmax(x: a, y: a, compare: fn(a, a) -> order.Order) -> #(a, a) {
 pub fn list_minimum(
   arr: List(a),
   compare: fn(a, a) -> order.Order,
-) -> Result(a, String) {
+) -> Result(a, Nil) {
   case arr {
-    [] ->
-      "Invalid input argument: The list is empty."
-      |> Error
+    [] -> Error(Nil)
     [x, ..rest] ->
       Ok(
         list.fold(rest, x, fn(acc: a, element: a) {
@@ -1003,11 +1001,9 @@ pub fn list_minimum(
 pub fn list_maximum(
   arr: List(a),
   compare: fn(a, a) -> order.Order,
-) -> Result(a, String) {
+) -> Result(a, Nil) {
   case arr {
-    [] ->
-      "Invalid input argument: The list is empty."
-      |> Error
+    [] -> Error(Nil)
     [x, ..rest] ->
       Ok(
         list.fold(rest, x, fn(acc: a, element: a) {
@@ -1063,11 +1059,9 @@ pub fn list_maximum(
 pub fn arg_minimum(
   arr: List(a),
   compare: fn(a, a) -> order.Order,
-) -> Result(List(Int), String) {
+) -> Result(List(Int), Nil) {
   case arr {
-    [] ->
-      "Invalid input argument: The list is empty."
-      |> Error
+    [] -> Error(Nil)
     _ -> {
       let assert Ok(min) =
         arr
@@ -1133,11 +1127,9 @@ pub fn arg_minimum(
 pub fn arg_maximum(
   arr: List(a),
   compare: fn(a, a) -> order.Order,
-) -> Result(List(Int), String) {
+) -> Result(List(Int), Nil) {
   case arr {
-    [] ->
-      "Invalid input argument: The list is empty."
-      |> Error
+    [] -> Error(Nil)
     _ -> {
       let assert Ok(max) =
         arr
@@ -1203,11 +1195,9 @@ pub fn arg_maximum(
 pub fn extrema(
   arr: List(a),
   compare: fn(a, a) -> order.Order,
-) -> Result(#(a, a), String) {
+) -> Result(#(a, a), Nil) {
   case arr {
-    [] ->
-      "Invalid input argument: The list is empty."
-      |> Error
+    [] -> Error(Nil)
     [x, ..rest] ->
       Ok(
         list.fold(rest, #(x, x), fn(acc: #(a, a), element: a) {
diff --git a/src/gleam_community/maths/predicates.gleam b/src/gleam_community/maths/predicates.gleam
index 9cdd74c..37391e9 100644
--- a/src/gleam_community/maths/predicates.gleam
+++ b/src/gleam_community/maths/predicates.gleam
@@ -20,12 +20,12 @@
 ////<style>
 ////    .katex { font-size: 1.1em; }
 ////</style>
-//// 
+////
 //// ---
-//// 
-//// Predicates: A module containing functions for testing various mathematical 
+////
+//// Predicates: A module containing functions for testing various mathematical
 //// properties of numbers.
-//// 
+////
 //// * **Tests**
 ////   * [`is_close`](#is_close)
 ////   * [`list_all_close`](#all_close)
@@ -38,7 +38,7 @@
 ////   * [`is_divisible`](#is_divisible)
 ////   * [`is_multiple`](#is_multiple)
 ////   * [`is_prime`](#is_prime)
-//// 
+////
 
 import gleam/int
 import gleam/list
@@ -54,16 +54,16 @@ import gleam_community/maths/piecewise
 ///     </a>
 /// </div>
 ///
-/// Determine if a given value \\(a\\) is close to or equivalent to a reference value 
+/// Determine if a given value \\(a\\) is close to or equivalent to a reference value
 /// \\(b\\) based on supplied relative \\(r_{tol}\\) and absolute \\(a_{tol}\\) tolerance
-/// values. The equivalance of the two given values are then determined based on 
+/// values. The equivalance of the two given values are then determined based on
 /// the equation:
 ///
 /// \\[
 ///     \|a - b\| \leq (a_{tol} + r_{tol} \cdot \|b\|)
 /// \\]
 ///
-/// `True` is returned if statement holds, otherwise `False` is returned. 
+/// `True` is returned if statement holds, otherwise `False` is returned.
 /// <details>
 ///     <summary>Example</summary>
 ///
@@ -135,7 +135,7 @@ fn float_absolute_difference(a: Float, b: Float) -> Float {
 ///       let rtol: Float = 0.01
 ///       let atol: Float = 0.10
 ///       predicates.all_close(xarr, yarr, rtol, atol)
-///       |> fn(zarr: Result(List(Bool), String)) -> Result(Bool, Nil) {
+///       |> fn(zarr: Result(List(Bool), Nil)) -> Result(Bool, Nil) {
 ///         case zarr {
 ///           Ok(arr) ->
 ///             arr
@@ -159,13 +159,11 @@ pub fn all_close(
   yarr: List(Float),
   rtol: Float,
   atol: Float,
-) -> Result(List(Bool), String) {
+) -> Result(List(Bool), Nil) {
   let xlen: Int = list.length(xarr)
   let ylen: Int = list.length(yarr)
   case xlen == ylen {
-    False ->
-      "Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)."
-      |> Error
+    False -> Error(Nil)
     True ->
       list.zip(xarr, yarr)
       |> list.map(fn(z: #(Float, Float)) -> Bool {
@@ -182,10 +180,10 @@ pub fn all_close(
 /// </div>
 ///
 /// Determine if a given value is fractional.
-/// 
-/// `True` is returned if the given value is fractional, otherwise `False` is 
-/// returned. 
-/// 
+///
+/// `True` is returned if the given value is fractional, otherwise `False` is
+/// returned.
+///
 /// <details>
 ///     <summary>Example</summary>
 ///
@@ -195,7 +193,7 @@ pub fn all_close(
 ///     pub fn example () {
 ///       predicates.is_fractional(0.3333)
 ///       |> should.equal(True)
-///       
+///
 ///       predicates.is_fractional(1.0)
 ///       |> should.equal(False)
 ///     }
@@ -222,7 +220,7 @@ fn do_ceiling(a: Float) -> Float
 /// </div>
 ///
 /// A function that tests whether a given integer value \\(x \in \mathbb{Z}\\) is a
-/// power of another integer value \\(y \in \mathbb{Z}\\).  
+/// power of another integer value \\(y \in \mathbb{Z}\\).
 ///
 /// <details>
 ///     <summary>Example:</summary>
@@ -262,9 +260,9 @@ pub fn is_power(x: Int, y: Int) -> Bool {
 /// </div>
 ///
 /// A function that tests whether a given integer value \\(n \in \mathbb{Z}\\) is a
-/// perfect number. A number is perfect if it is equal to the sum of its proper 
+/// perfect number. A number is perfect if it is equal to the sum of its proper
 /// positive divisors.
-/// 
+///
 /// <details>
 ///     <summary>Details</summary>
 ///
@@ -314,7 +312,7 @@ fn do_sum(arr: List(Int)) -> Int {
 ///     </a>
 /// </div>
 ///
-/// A function that tests whether a given integer value \\(x \in \mathbb{Z}\\) is even.  
+/// A function that tests whether a given integer value \\(x \in \mathbb{Z}\\) is even.
 ///
 /// <details>
 ///     <summary>Example:</summary>
@@ -325,7 +323,7 @@ fn do_sum(arr: List(Int)) -> Int {
 ///     pub fn example() {
 ///       predicates.is_even(-3)
 ///       |> should.equal(False)
-///     
+///
 ///       predicates.is_even(-4)
 ///       |> should.equal(True)
 ///     }
@@ -347,7 +345,7 @@ pub fn is_even(x: Int) -> Bool {
 ///     </a>
 /// </div>
 ///
-/// A function that tests whether a given integer value \\(x \in \mathbb{Z}\\) is odd.  
+/// A function that tests whether a given integer value \\(x \in \mathbb{Z}\\) is odd.
 ///
 /// <details>
 ///     <summary>Example:</summary>
@@ -358,7 +356,7 @@ pub fn is_even(x: Int) -> Bool {
 ///     pub fn example() {
 ///       predicates.is_odd(-3)
 ///       |> should.equal(True)
-///     
+///
 ///       predicates.is_odd(-4)
 ///       |> should.equal(False)
 ///     }
@@ -380,24 +378,24 @@ pub fn is_odd(x: Int) -> Bool {
 ///     </a>
 /// </div>
 ///
-/// A function that tests whether a given integer value \\(x \in \mathbb{Z}\\) is a 
-/// prime number. A prime number is a natural number greater than 1 that has no 
+/// A function that tests whether a given integer value \\(x \in \mathbb{Z}\\) is a
+/// prime number. A prime number is a natural number greater than 1 that has no
 /// positive divisors other than 1 and itself.
-/// 
-/// The function uses the Miller-Rabin primality test to assess if \\(x\\) is prime. 
-/// It is a probabilistic test, so it can mistakenly identify a composite number 
+///
+/// The function uses the Miller-Rabin primality test to assess if \\(x\\) is prime.
+/// It is a probabilistic test, so it can mistakenly identify a composite number
 /// as prime. However, the probability of such errors decreases with more testing
-/// iterations (the function uses 64 iterations internally, which is typically 
+/// iterations (the function uses 64 iterations internally, which is typically
 /// more than sufficient). The Miller-Rabin test is particularly useful for large
 /// numbers.
-/// 
+///
 /// <details>
 ///     <summary>Details</summary>
 ///
 ///   Examples of prime numbers:
 ///   - \\(2\\) is a prime number since it has only two divisors: \\(1\\) and \\(2\\).
 ///   - \\(7\\) is a prime number since it has only two divisors: \\(1\\) and \\(7\\).
-///   - \\(4\\) is not a prime number since it has divisors other than \\(1\\) and itself, such 
+///   - \\(4\\) is not a prime number since it has divisors other than \\(1\\) and itself, such
 ///     as \\(2\\).
 ///
 /// </details>
@@ -414,7 +412,7 @@ pub fn is_odd(x: Int) -> Bool {
 ///
 ///       predicates.is_prime(4)
 ///       |> should.equal(False)
-///       
+///
 ///       // Test the 2nd Carmichael number
 ///       predicates.is_prime(1105)
 ///       |> should.equal(False)
@@ -512,9 +510,9 @@ pub fn is_between(x: Float, lower: Float, upper: Float) -> Bool {
 ///     </a>
 /// </div>
 ///
-/// A function that tests whether a given integer \\(n \in \mathbb{Z}\\) is divisible by another 
+/// A function that tests whether a given integer \\(n \in \mathbb{Z}\\) is divisible by another
 /// integer \\(d \in \mathbb{Z}\\), such that \\(n \mod d = 0\\).
-/// 
+///
 /// <details>
 ///     <summary>Details</summary>
 ///
@@ -555,9 +553,9 @@ pub fn is_divisible(n: Int, d: Int) -> Bool {
 ///     </a>
 /// </div>
 ///
-/// A function that tests whether a given integer \\(m \in \mathbb{Z}\\) is a multiple of another 
+/// A function that tests whether a given integer \\(m \in \mathbb{Z}\\) is a multiple of another
 /// integer \\(k \in \mathbb{Z}\\), such that \\(m = k \times q\\), with \\(q \in \mathbb{Z}\\).
-/// 
+///
 /// <details>
 ///     <summary>Details</summary>
 ///
diff --git a/src/gleam_community/maths/sequences.gleam b/src/gleam_community/maths/sequences.gleam
index 9ffe6ec..daff369 100644
--- a/src/gleam_community/maths/sequences.gleam
+++ b/src/gleam_community/maths/sequences.gleam
@@ -20,18 +20,18 @@
 ////<style>
 ////    .katex { font-size: 1.1em; }
 ////</style>
-//// 
+////
 //// ---
-//// 
-//// Sequences: A module containing functions for generating various types of 
+////
+//// Sequences: A module containing functions for generating various types of
 //// sequences, ranges and intervals.
-//// 
+////
 //// * **Ranges and intervals**
 ////   * [`arange`](#arange)
 ////   * [`linear_space`](#linear_space)
 ////   * [`logarithmic_space`](#logarithmic_space)
 ////   * [`geometric_space`](#geometric_space)
-//// 
+////
 
 import gleam/iterator
 import gleam_community/maths/conversion
@@ -47,7 +47,7 @@ import gleam_community/maths/piecewise
 /// The function returns an iterator generating evenly spaced values within a given interval.
 /// based on a start value but excludes the stop value. The spacing between values is determined
 /// by the step size provided. The function supports both positive and negative step values.
-/// 
+///
 /// <details>
 ///     <summary>Example:</summary>
 ///
@@ -59,13 +59,13 @@ import gleam_community/maths/piecewise
 ///       sequences.arange(1.0, 5.0, 1.0)
 ///       |> iterator.to_list()
 ///       |> should.equal([1.0, 2.0, 3.0, 4.0])
-///       
+///
 ///       // No points returned since
 ///       // start is smaller than stop and the step is positive
 ///       sequences.arange(5.0, 1.0, 1.0)
 ///       |> iterator.to_list()
 ///       |> should.equal([])
-///       
+///
 ///       // Points returned since
 ///       // start smaller than stop but negative step
 ///       sequences.arange(5.0, 1.0, -1.0)
@@ -115,10 +115,10 @@ pub fn arange(
 ///     </a>
 /// </div>
 ///
-/// The function returns an iterator for generating linearly spaced points over a specified 
-/// interval. The endpoint of the interval can optionally be included/excluded. The number of 
+/// The function returns an iterator for generating linearly spaced points over a specified
+/// interval. The endpoint of the interval can optionally be included/excluded. The number of
 /// points and whether the endpoint is included determine the spacing between values.
-/// 
+///
 /// <details>
 ///     <summary>Example:</summary>
 ///
@@ -138,7 +138,7 @@ pub fn arange(
 ///           0.0,
 ///           tol,
 ///         )
-///     
+///
 ///       result
 ///       |> list.all(fn(x) { x == True })
 ///       |> should.be_true()
@@ -160,7 +160,7 @@ pub fn linear_space(
   stop: Float,
   num: Int,
   endpoint: Bool,
-) -> Result(iterator.Iterator(Float), String) {
+) -> Result(iterator.Iterator(Float), Nil) {
   let direction: Float = case start <=. stop {
     True -> 1.0
     False -> -1.0
@@ -184,9 +184,7 @@ pub fn linear_space(
       })
       |> Ok
     }
-    False ->
-      "Invalid input: num < 1. Valid input is num >= 1."
-      |> Error
+    False -> Error(Nil)
   }
 }
 
@@ -196,10 +194,10 @@ pub fn linear_space(
 ///     </a>
 /// </div>
 ///
-/// The function returns an iterator of logarithmically spaced points over a specified interval. 
-/// The endpoint of the interval can optionally be included/excluded. The number of points, base, 
+/// The function returns an iterator of logarithmically spaced points over a specified interval.
+/// The endpoint of the interval can optionally be included/excluded. The number of points, base,
 /// and whether the endpoint is included determine the spacing between values.
-/// 
+///
 /// <details>
 ///     <summary>Example:</summary>
 ///
@@ -241,7 +239,7 @@ pub fn logarithmic_space(
   num: Int,
   endpoint: Bool,
   base: Float,
-) -> Result(iterator.Iterator(Float), String) {
+) -> Result(iterator.Iterator(Float), Nil) {
   case num > 0 {
     True -> {
       let assert Ok(linspace) = linear_space(start, stop, num, endpoint)
@@ -252,9 +250,7 @@ pub fn logarithmic_space(
       })
       |> Ok
     }
-    False ->
-      "Invalid input: num < 1. Valid input is num >= 1."
-      |> Error
+    False -> Error(Nil)
   }
 }
 
@@ -264,9 +260,9 @@ pub fn logarithmic_space(
 ///     </a>
 /// </div>
 ///
-/// The function returns an iterator of numbers spaced evenly on a log scale (a geometric 
-/// progression). Each point in the list is a constant multiple of the previous. The function is 
-/// similar to the [`logarithmic_space`](#logarithmic_space) function, but with endpoints 
+/// The function returns an iterator of numbers spaced evenly on a log scale (a geometric
+/// progression). Each point in the list is a constant multiple of the previous. The function is
+/// similar to the [`logarithmic_space`](#logarithmic_space) function, but with endpoints
 /// specified directly.
 ///
 /// <details>
@@ -295,7 +291,7 @@ pub fn logarithmic_space(
 ///       // Input (start and stop can't be equal to 0.0)
 ///       sequences.geometric_space(0.0, 1000.0, 3, False)
 ///       |> should.be_error()
-///     
+///
 ///       sequences.geometric_space(-1000.0, 0.0, 3, False)
 ///       |> should.be_error()
 ///
@@ -316,11 +312,9 @@ pub fn geometric_space(
   stop: Float,
   num: Int,
   endpoint: Bool,
-) -> Result(iterator.Iterator(Float), String) {
+) -> Result(iterator.Iterator(Float), Nil) {
   case start == 0.0 || stop == 0.0 {
-    True ->
-      "Invalid input: Neither 'start' nor 'stop' can be zero, as they must be non-zero for logarithmic calculations."
-      |> Error
+    True -> Error(Nil)
     False ->
       case num > 0 {
         True -> {
@@ -328,9 +322,7 @@ pub fn geometric_space(
           let assert Ok(log_stop) = elementary.logarithm_10(stop)
           logarithmic_space(log_start, log_stop, num, endpoint, 10.0)
         }
-        False ->
-          "Invalid input: num < 1. Valid input is num >= 1."
-          |> Error
+        False -> Error(Nil)
       }
   }
 }
diff --git a/src/gleam_community/maths/special.gleam b/src/gleam_community/maths/special.gleam
index f6e3438..ae5c8f6 100644
--- a/src/gleam_community/maths/special.gleam
+++ b/src/gleam_community/maths/special.gleam
@@ -20,17 +20,17 @@
 ////<style>
 ////    .katex { font-size: 1.1em; }
 ////</style>
-//// 
+////
 //// ---
-//// 
+////
 //// Special: A module containing special mathematical functions.
-//// 
+////
 //// * **Special mathematical functions**
 ////   * [`beta`](#beta)
 ////   * [`erf`](#erf)
 ////   * [`gamma`](#gamma)
 ////   * [`incomplete_gamma`](#incomplete_gamma)
-//// 
+////
 
 import gleam/list
 import gleam_community/maths/conversion
@@ -100,7 +100,7 @@ pub fn erf(x: Float) -> Float {
 ///     </a>
 /// </div>
 ///
-/// The gamma function over the real numbers. The function is essentially equal to 
+/// The gamma function over the real numbers. The function is essentially equal to
 /// the factorial for any positive integer argument: \\(\Gamma(n) = (n - 1)!\\)
 ///
 /// The implemented gamma function is approximated through Lanczos approximation
@@ -163,7 +163,7 @@ fn gamma_lanczos(x: Float) -> Float {
 ///     </a>
 /// </div>
 ///
-pub fn incomplete_gamma(a: Float, x: Float) -> Result(Float, String) {
+pub fn incomplete_gamma(a: Float, x: Float) -> Result(Float, Nil) {
   case a >. 0.0 && x >=. 0.0 {
     True -> {
       let assert Ok(v) = elementary.power(x, a)
@@ -173,9 +173,7 @@ pub fn incomplete_gamma(a: Float, x: Float) -> Result(Float, String) {
       |> Ok
     }
 
-    False ->
-      "Invalid input argument: a <= 0 or x < 0. Valid input is a > 0 and x >= 0."
-      |> Error
+    False -> Error(Nil)
   }
 }
 
diff --git a/test/gleam_community/maths/predicates_test.gleam b/test/gleam_community/maths/predicates_test.gleam
index fcae19c..955e46e 100644
--- a/test/gleam_community/maths/predicates_test.gleam
+++ b/test/gleam_community/maths/predicates_test.gleam
@@ -23,7 +23,7 @@ pub fn float_list_all_close_test() {
   let rtol: Float = 0.01
   let atol: Float = 0.1
   predicates.all_close(xarr, yarr, rtol, atol)
-  |> fn(zarr: Result(List(Bool), String)) -> Result(Bool, Nil) {
+  |> fn(zarr: Result(List(Bool), Nil)) -> Result(Bool, Nil) {
     case zarr {
       Ok(arr) ->
         arr