Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nat documentation and fromText() utility #517

Merged
merged 6 commits into from
Feb 6, 2023
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
249 changes: 238 additions & 11 deletions src/Nat.mo
Original file line number Diff line number Diff line change
@@ -1,72 +1,299 @@
/// Natural numbers
/// Utility functions for working with natural numbers.
Copy link
Contributor

@luc-blaeser luc-blaeser Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: (Just saw that it is mentioned below). But maybe it is useful to mention it also in the header that the numbers have infinite precision, in symmetry with the documentation Int.mo.

///
/// Most operations on natural numbers (e.g. addition) are available as built-in operators (e.g. `1 + 1`).
/// This module provides equivalent functions and `Text` conversion.
///
/// Import from the base library to use this module.
/// ```motoko name=import
/// import Nat "mo:base/Nat";
/// ```

import Int "Int";
import Order "Order";
import Prim "mo:⛔";
import Char "Char";

module {

/// Infinite precision natural numbers.
public type Nat = Prim.Types.Nat;

/// Conversion.
public let toText : Nat -> Text = Int.toText;
/// Converts a natural number to its textual representation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detail (unsure whether worth mentioning): Without underscore formatting for thousand digit separators.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add

///
/// Example:
/// ```motoko include=import
/// Nat.toText 1234 // => "1234"
/// ```
public func toText(n : Nat) : Text = Int.toText n;

/// Creates a natural number from its textual representation. Returns `null`
/// if the input is not a valid natural number.
///
/// Example:
/// ```motoko include=import
/// Nat.fromText "1234" // => ?1234
/// ```
public func fromText(text : Text) : ?Nat {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed that we do not have this function in Int. Maybe it would be useful to offer it there two (and even combine the implementation, similar to toText).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#520 Yeah that sounds good!

if (text == "") {
return null
};
var n = 0;
for (c in text.chars()) {
if (Char.isDigit(c)) {
let charAsNat = Prim.nat32ToNat(Prim.charToNat32(c) -% Prim.charToNat32('0'));
n := n * 10 + charAsNat
} else {
return null
}
};
?n
};

/// Returns the minimum of `x` and `y`.
///
/// Example:
/// ```motoko include=import
/// Nat.min(1, 2) // => 1
/// ```
public func min(x : Nat, y : Nat) : Nat {
if (x < y) { x } else { y }
};

/// Returns the maximum of `x` and `y`.
///
/// Example:
/// ```motoko include=import
/// Nat.max(1, 2) // => 2
/// ```
public func max(x : Nat, y : Nat) : Nat {
if (x < y) { y } else { x }
};

/// Returns `x == y`.
/// Equality function for Nat types.
/// This is equivalent to `x == y`.
///
/// Example:
/// ```motoko include=import
/// ignore Nat.equal(1, 1); // => true
/// 1 == 1 // => true
/// ```
///
/// Note: The reason why this function is defined in this library (in addition
/// to the existing `==` operator) is so that you can use it as a function
/// value to pass to a higher order function. It is not possible to use `==`
/// as a function value at the moment.
///
Copy link
Contributor

@luc-blaeser luc-blaeser Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great comment. Maybe we should also improve Int with this explanation.

/// Example:
/// ```motoko include=import
/// import Buffer "mo:base/Buffer";
///
/// let buffer1 = Buffer.Buffer<Nat>(3);
/// let buffer2 = Buffer.Buffer<Nat>(3);
/// Buffer.equal(buffer1, buffer2, Nat.equal) // => true
/// ```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And nice example demonstrating where it makes really sense to use the equal function.

public func equal(x : Nat, y : Nat) : Bool { x == y };

/// Returns `x != y`.
/// Inequality function for Nat types.
/// This is equivalent to `x != y`.
///
/// Example:
/// ```motoko include=import
/// ignore Nat.notEqual(1, 2); // => true
/// 1 != 2 // => true
/// ```
///
/// Note: The reason why this function is defined in this library (in addition
/// to the existing `!=` operator) is so that you can use it as a function
/// value to pass to a higher order function. It is not possible to use `!=`
/// as a function value at the moment.
public func notEqual(x : Nat, y : Nat) : Bool { x != y };

/// Returns `x < y`.
/// "Less than" function for Nat types.
/// This is equivalent to `x < y`.
///
/// Example:
/// ```motoko include=import
/// ignore Nat.less(1, 2); // => true
/// 1 < 2 // => true
/// ```
///
/// Note: The reason why this function is defined in this library (in addition
/// to the existing `<` operator) is so that you can use it as a function
/// value to pass to a higher order function. It is not possible to use `<`
/// as a function value at the moment.
public func less(x : Nat, y : Nat) : Bool { x < y };

/// Returns `x <= y`.
/// "Less than or equal" function for Nat types.
/// This is equivalent to `x <= y`.
///
/// Example:
/// ```motoko include=import
/// ignore Nat.lessOrEqual(1, 2); // => true
/// 1 <= 2 // => true
/// ```
///
/// Note: The reason why this function is defined in this library (in addition
/// to the existing `<=` operator) is so that you can use it as a function
/// value to pass to a higher order function. It is not possible to use `<=`
/// as a function value at the moment.
public func lessOrEqual(x : Nat, y : Nat) : Bool { x <= y };

/// Returns `x > y`.
/// "Greater than" function for Nat types.
/// This is equivalent to `x > y`.
///
/// Example:
/// ```motoko include=import
/// ignore Nat.greater(2, 1); // => true
/// 2 > 1 // => true
/// ```
///
/// Note: The reason why this function is defined in this library (in addition
/// to the existing `>` operator) is so that you can use it as a function
/// value to pass to a higher order function. It is not possible to use `>`
/// as a function value at the moment.
public func greater(x : Nat, y : Nat) : Bool { x > y };

/// Returns `x >= y`.
/// "Greater than or equal" function for Nat types.
/// This is equivalent to `x >= y`.
///
/// Example:
/// ```motoko include=import
/// ignore Nat.greaterOrEqual(2, 1); // => true
/// 2 >= 1 // => true
/// ```
///
/// Note: The reason why this function is defined in this library (in addition
/// to the existing `>=` operator) is so that you can use it as a function
/// value to pass to a higher order function. It is not possible to use `>=`
/// as a function value at the moment.
public func greaterOrEqual(x : Nat, y : Nat) : Bool { x >= y };

/// Returns the order of `x` and `y`.
/// General purpose comparison function for `Nat`. Returns the `Order` (
/// either `#less`, `#equal`, or `#greater`) of comparing `x` with `y`.
///
/// Example:
/// ```motoko include=import
/// Nat.compare(2, 3) // => #less
/// ```
///
/// Note: The reason why this function is defined in this library (in addition
/// to the existing `>=` operator) is so that you can use it as a function
/// value to pass to a higher order function. It is not possible to use `>=`
/// as a function value at the moment.
///
/// Example:
/// ```motoko include=import
/// import Array "mo:base/Array";
/// Array.sort([2, 3, 1], Nat.compare) // => [1, 2, 3]
/// ```
public func compare(x : Nat, y : Nat) : { #less; #equal; #greater } {
if (x < y) { #less } else if (x == y) { #equal } else { #greater }
};

/// Returns the sum of `x` and `y`, `x + y`.
///
/// Example:
Copy link
Contributor

@luc-blaeser luc-blaeser Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it could help to mention (again): No overflow since Nat has infinite precision. Same for mul().

/// ```motoko include=import
/// ignore Nat.add(1, 2); // => 3
/// 1 + 2 // => 3
/// ```
///
/// Note: The reason why this function is defined in this library (in addition
/// to the existing `+` operator) is so that you can use it as a function
/// value to pass to a higher order function. It is not possible to use `+`
/// as a function value at the moment.
///
/// Example:
/// ```motoko include=import
/// import Array "mo:base/Array";
/// Array.foldLeft([2, 3, 1], 0, Nat.add) // => 6
/// ```
public func add(x : Nat, y : Nat) : Nat { x + y };

/// Returns the difference of `x` and `y`, `x - y`.
/// Traps on underflow.
/// Traps on underflow below `0`.
///
/// Example:
/// ```motoko include=import
/// ignore Nat.sub(2, 1); // => 1
/// // Add a type annotation to avoid a warning about the subtraction
/// 2 - 1 : Nat // => 1
/// ```
///
/// Note: The reason why this function is defined in this library (in addition
/// to the existing `-` operator) is so that you can use it as a function
/// value to pass to a higher order function. It is not possible to use `-`
/// as a function value at the moment.
///
/// Example:
/// ```motoko include=import
/// import Array "mo:base/Array";
/// Array.foldLeft([2, 3, 1], 10, Nat.sub) // => 4
/// ```
public func sub(x : Nat, y : Nat) : Nat { x - y };

/// Returns the product of `x` and `y`, `x * y`.
///
/// Example:
/// ```motoko include=import
/// ignore Nat.mul(2, 3); // => 6
/// 2 * 3 // => 6
/// ```
///
/// Note: The reason why this function is defined in this library (in addition
/// to the existing `*` operator) is so that you can use it as a function
/// value to pass to a higher order function. It is not possible to use `*`
/// as a function value at the moment.
///
/// Example:
/// ```motoko include=import
/// import Array "mo:base/Array";
/// Array.foldLeft([2, 3, 1], 1, Nat.mul) // => 6
/// ```
public func mul(x : Nat, y : Nat) : Nat { x * y };

/// Returns the division of `x` by `y`, `x / y`.
/// Traps when `y` is zero.
Copy link
Contributor

@luc-blaeser luc-blaeser Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Traps when `y` is zero.
/// Returns the unsigned integer division of `x` by `y`, `x / y`.
/// Rounds the quotient towards zero, which is the same as truncating the decimal places of the quotient.

///
/// Example:
/// ```motoko include=import
/// ignore Nat.div(6, 2); // => 3
/// 6 / 2 // => 3
/// ```
///
/// Note: The reason why this function is defined in this library (in addition
/// to the existing `/` operator) is so that you can use it as a function
/// value to pass to a higher order function. It is not possible to use `/`
/// as a function value at the moment.
public func div(x : Nat, y : Nat) : Nat { x / y };

/// Returns the remainder of `x` divided by `y`, `x % y`.
/// Traps when `y` is zero.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to div, the remainder of the unsigned integer division.

///
/// Example:
/// ```motoko include=import
/// ignore Nat.rem(6, 4); // => 2
/// 6 % 4 // => 2
/// ```
///
/// Note: The reason why this function is defined in this library (in addition
/// to the existing `%` operator) is so that you can use it as a function
/// value to pass to a higher order function. It is not possible to use `%`
/// as a function value at the moment.
public func rem(x : Nat, y : Nat) : Nat { x % y };

/// Returns `x` to the power of `y`, `x ** y`.
///
Copy link
Contributor

@luc-blaeser luc-blaeser Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
///
/// Traps when `y > 2 ** 32`.
/// No overflow since `Nat` has infinite precision.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! Didn't know that

/// Example:
/// ```motoko include=import
/// ignore Nat.pow(2, 3); // => 8
/// 2 ** 3 // => 8
/// ```
///
/// Note: The reason why this function is defined in this library (in addition
/// to the existing `**` operator) is so that you can use it as a function
/// value to pass to a higher order function. It is not possible to use `**`
/// as a function value at the moment.
public func pow(x : Nat, y : Nat) : Nat { x ** y };

}