module Data.Nat.Base where

Natural numbers🔗

The natural numbers are the inductive type generated by zero and closed under taking successors. Thus, they satisfy the following induction principle, which is familiar:

Nat-elim :  {} (P : Nat  Type )
          P 0
          ({n : Nat}  P n  P (suc n))
          (n : Nat)  P n
Nat-elim P pz ps zero    = pz
Nat-elim P pz ps (suc n) = ps (Nat-elim P pz ps n)

iter :  {} {A : Type }  Nat  (A  A)  A  A
iter zero f = id
iter (suc n) f = f  iter n f

Translating from type theoretic notation to mathematical English, the type of Nat-elim says that if a predicate P holds of zero, and the truth of P(suc n) follows from P(n), then P is true for every natural number.

Discreteness🔗

An interesting property of the natural numbers, type-theoretically, is that they are discrete: given any pair of natural numbers, there is an algorithm that can tell you whether or not they are equal. First, observe that we can distinguish zero from successor:

zero≠suc : {n : Nat}  ¬ zero  suc n
zero≠suc path = subst distinguish path tt where
  distinguish : Nat  Type
  distinguish zero = 
  distinguish (suc x) = 

The idea behind this proof is that we can write a predicate which is true for zero, and false for any successor. Since we know that is inhabited (by tt), we can transport that along the claimed path to get an inhabitant of , i.e., a contradiction.

pred : Nat  Nat
pred 0 = 0
pred (suc n) = n

suc-inj : {x y : Nat}  suc x  suc y  x  y
suc-inj = ap pred

Furthermore, observe that the successor operation is injective, i.e., we can “cancel” it on paths. Putting these together, we get a proof that equality for the natural numbers is decidable:

  Discrete-Nat : Discrete Nat
  Discrete-Nat .decide = go where
    go :  x y  Dec (x  y)
    go zero zero    = yes refl
    go zero (suc y) = no λ zero≡suc  absurd (zero≠suc zero≡suc)
    go (suc x) zero = no λ suc≡zero  absurd (suc≠zero suc≡zero)
    go (suc x) (suc y) with go x y
    ... | yes x≡y = yes (ap suc x≡y)
    ... | no ¬x≡y = no λ sucx≡sucy  ¬x≡y (suc-inj sucx≡sucy)

Hedberg’s theorem implies that Nat is a set, i.e., it only has trivial paths.

opaque
  Nat-is-set : is-set Nat
  Nat-is-set = Discrete→is-set Discrete-Nat

instance
  H-Level-Nat :  {n}  H-Level Nat (2 + n)
  H-Level-Nat = basic-instance 2 Nat-is-set

Arithmetic🔗

\ Warning

Heads up! The arithmetic properties of operations on the natural numbers are in the module Data.Nat.Properties.

Mikan ships with preferred definitions of _+_ and _*_ which are optimised to direct computation on machine integers when applied to literals. These are defined in the built-in module [Prim.Data.Nat].

_ = _+_
_ = _*_

There is no built-in exponentiation operator, but we can define _^_ by recursion on the exponent.

_^_ : Nat  Nat  Nat
x ^ zero = 1
x ^ suc y = x * (x ^ y)

infixr 10 _^_

Ordering🔗

We define the order relation _≤_ on the natural numbers by appealing to the decision procedure _≤?_.

record _≤_ (x y : Nat) : Type where
  constructor lift
  field
    lower : So (x ≤? y)

Our choice of defining _≤_ as a record wrapping a recursive boolean computation is slightly peculiar from a formalisation perspective. However, it turns out to be pretty much optimal:

  • Like an indexed inductive type, but unlike directly computing a type by recursion, a record is definitionally injective in its arguments.

    This means that any function that has _≤_ arguments can take the numbers as implicit arguments.

  • Unlike an indexed inductive type, we can arrange for a record type to be a definitional proposition, which means neither us nor the conversion checker need to spend time comparing elements of _≤_.

≤-is-prop : {x y : Nat}  is-prop (x  y)
≤-is-prop p q = refl
  • Finally, we could imagine a record type wrapping a strict proposition computed by recursion. We opted against this for two reasons: first, re-using the wrapper type So limits the amount of code that needs to deal with the “weird” universe SProp.

    Second, a hand-written definition by recursion would have to fully traverse the numbers, in unary, until one of them is zero. Wrapping the built-in decision procedure _≤?_ shortcuts evaluation when the numbers are literals, meaning we don’t lose any type-checking time inspecting very large numeric literals.

_ : 2 ^ 1024  2 ^ 2048
_ = lift oh

We define the strict ordering on Nat as well, re-using the definition of _≤_.

_<_ : Nat  Nat  Type
m < n = suc m  n
infix 7 _<_ _≤_

As an “ordering combinator”, we can define the maximum of two natural numbers by recursion: The maximum of zero and a successor (on either side) is the successor, and the maximum of successors is the successor of their maximum.

max : Nat  Nat  Nat
max zero zero = zero
max zero (suc y) = suc y
max (suc x) zero = suc x
max (suc x) (suc y) = suc (max x y)

Similarly, we can define the minimum of two numbers:

min : Nat  Nat  Nat
min zero zero = zero
min zero (suc y) = zero
min (suc x) zero = zero
min (suc x) (suc y) = suc (min x y)