Type Bounds
When we express a subtype-supertype relationship by writing T <: U
, then we say that T
is a subtype by U
. We can use this relationship between two types in the instantiation of generic types in functions.
Type bounds in functions
Consider the following function signature:
func makeNat<T <: Number>(x : T) : Natural
It's a generic function that specifies a type bound Number
for its generic type parameter T
. This is expressed as <T <: Number>
.
The function takes an argument of generic bounded type T
and returns a value of type Natural
. The function is meant to take a general kind of number and process it into a Nat
.
The types Number
and Natural
are declared like this:
type Natural = {
#N : Nat;
};
type Integer = {
#I : Int;
};
type Floating = {
#F : Float;
};
type Number = Natural or Integer or Floating;
The types Natural
, Integer
and Floating
are just variants with one field and associated types Nat
, Int
and Float
respectively.
The Number
type is a type union of Natural
, Integer
and Floating
. A type union is constructed using the or
keyword. This means that a Number
could be either a Natural
, an Integer
or a Floating
.
We would use these types to implement our function like this:
import Int "mo:base/Int";
import Float "mo:base/Float";
func makeNat<T <: Number>(x : T) : Natural {
switch (x) {
case (#N n) {
#N n;
};
case (#I i) {
#N(Int.abs(i));
};
case (#F f) {
let rounded = Float.nearest(f);
let integer = Float.toInt(rounded);
let natural = Int.abs(integer);
#N natural;
};
};
};
After importing the Int and Float modules from the Base Library, we declare our function and implement a switch expression for the argument x
.
In case we find a #N
we know we are dealing with a Natural
and thus immediately return the the same variant and associated value that we refer to as n
.
In case we find an #I
we know we are dealing with an Integer
and thus take the associated value i
and apply the abs()
function from the Int module to turn the Int
into a Nat
. We return a value of type Natural
once again.
In case we find a #F
we know we are dealing with a Floating
. So we take the associated value f
of type Float
, round it off and convert it to an Int
using functions from the Float module and convert to a Nat
again to return a value of type Natural
once again.
Lets test our function using some assertions:
assert makeNat(#N 0) == #N 0;
assert makeNat(#I(-10)) == #N 10;
assert makeNat(#F(-5.9)) == #N 6;
We use arguments of type Natural
, Integer
and Floating
with associated types Nat
, Int
and Float
respectively. They all are accepted by our function.
In all three cases, we get back a value of type Natural
with an associated value of type Nat
.
The Any
and None
types
All types in Motoko are bounded by a special type, namely the Any
type. This type is the supertype of all types and thus all types are a subtype of the Any
type. We may refer to it as the top type. Any value or expression in Motoko can be of type Any
.
Another special type in Motoko is the None
type. This type is the subtype of all types and thus all types are a supertype of None
. We may refer to it as the bottom type. No value in Motoko can have the None
type, but some expressions can.
NOTE
Even though no value has typeNone
, it is still useful for typing expressions that don't produce a value, such as infinite loops, early exits viareturn
andthrow
and other constructs that divert control-flow (like Debug.trap :Text -> None
)
For any type T
in Motoko, the following subtype-supertype relationship holds:
None <: T
T <: Any