module Data.Int where
Integers🔗
The integers are what you get when you complete the additive monoid structure on the naturals into a group. In non-cubical Agda, a representation of the integers as a coproduct with one of the factors offset (to avoid having two zeroes) is adopted. In Cubical Agda we can adopt a representation much closer to a “classical” construction of the integers:
data Int : Type where
: (x y : Nat) → Int
diff : (m n : Nat) → diff m n ≡ diff (suc m) (suc n) quot
This is an alternative representation of the construction of integers
as pairs
where
iff
:
An integer is an equivalence class of pairs of naturals, where
is identified with
,
or, more type-theoretically, the integers are generated by the
constructor diff
which embeds a
pair of naturals, and the path constructor quot
which expresses that
=
.
This single generating path is enough to recover the “classical” quotient, which we do in steps. First, we… prove that that is :
: (n : Nat) → diff 0 0 ≡ diff n n
zeroes = refl
zeroes zero (suc n) = zeroes n ∙ quot _ _ zeroes
Additionally, offsetting a difference by a fixed natural, as long as it’s done on both sides of the difference, does not change which integer is being represented: That is, considering all three naturals as integers, .
: (a b n : Nat) → diff a b ≡ diff (n + a) (n + b)
cancel = refl
cancel a b zero (suc n) = cancel a b n ∙ quot _ _ cancel a b
As a final pair of helper lemmas, we find that if and differ by an absolute value of , then the values and are the same (as long as we fix the sign — hence the two lemmas). The generic situation of “differing by ” is captured by fixing a natural number and adding , because we have and .
: (a b : Nat) → diff a (a + b) ≡ diff 0 b
offset-negative = refl
offset-negative zero b (suc a) b =
offset-negative (suc a) (suc (a + b)) ≡⟨ sym (quot _ _) ⟩
diff (a + b) ≡⟨ offset-negative a b ⟩
diff a 0 b ∎
diff
: (a b : Nat) → diff (a + b) a ≡ diff b 0
offset-positive = refl
offset-positive zero b (suc a) b =
offset-positive (suc (a + b)) (suc a) ≡⟨ sym (quot _ _) ⟩
diff (a + b) a ≡⟨ offset-positive a b ⟩
diff 0 ∎ diff b
Those two are the last two lemmas we need to prove the “if” direction
of “naturals are identified in the quotient iff they represent the same
difference”: the construction same-difference
below packages everything
together with a bow on the top.
opaque: {a b c d : Nat} → a + d ≡ b + c → diff a b ≡ diff c d
same-difference {zero} {b} {c} {d} path = sym $
same-difference
diff c ⌜ d ⌝ ≡⟨ ap! path ⟩(+-commutative b c) ⟩
diff c ⌜ b + c ⌝ ≡⟨ ap! (c + b) ≡⟨ offset-negative _ _ ⟩
diff c 0 b ∎
diff {suc a} {zero} {c} {d} path =
same-difference ( diff ⌜ c ⌝ d ≡⟨ ap! (sym path) ⟩
sym (+-commutative (suc a) d) ⟩
diff ⌜ suc a + d ⌝ d ≡⟨ ap! (d + suc a) d ≡⟨ offset-positive _ _ ⟩
diff (suc a) 0 ∎
diff )
{suc a} {suc b} {c} {d} path =
same-difference (suc a) (suc b) ≡˘⟨ quot _ _ ⟩
diff (suc-inj path) ⟩
diff a b ≡⟨ same-difference diff c d ∎
In the other direction, we must be clever: we use path induction,
defining a type family
such that the fibre of
over
is
.
We can then use path induction to construct the map inverse to same-difference
. On the way, the first
thing we establish is a pair of observations about equalities on the
natural numbers:
and
are equivalent conditions. This can be seen by commutativity and
injectivity of the successor function, but below we prove it using
equational reasoning, without appealing to commutativity.
module ℤ-Path where
private
variable a b m n c d : Nat
: (a + n ≡ b + m) → (a + suc n ≡ b + suc m)
encode-p-from {a = a} {n} {b} {m} p =
encode-p-from
a + suc n ≡⟨ +-sucr a n ⟩(a + n) ≡⟨ ap suc p ⟩
suc (b + m) ≡˘⟨ +-sucr b m ⟩
suc
b + suc m ∎
: (a + suc n ≡ b + suc m) → (a + n ≡ b + m)
encode-p-to {a} {n} {b} {m} p = suc-inj (sym (+-sucr a n) ∙ p ∙ +-sucr b m) encode-p-to
We then define, fixing two natural numbers
,
the family
by recursion on the integer
.
Recall that we want the fibre over
to be
,
so that’s our pick. Now, the quot
path constructor mandates that the fibre over
be the same as that over
— but this follows by propositional extensionality and the pair of
observations above.
: ∀ (a b : Nat) (x : Int) → Type
Code (diff c d) = a + d ≡ b + c
Code a b (quot m n i) = path i where
Code a b : (a + n ≡ b + m) ≡ (a + suc n ≡ b + suc m)
path = ua (prop-ext (Nat-is-set _ _) (Nat-is-set _ _) encode-p-from encode-p-to) path
Hence, if we have a path
,
we can apply path induction, whence it suffices to consider the case
where
is literally_ the difference of
and
.
To lift this into our Code
fibration, we must show that
,
but this is exactly commutativity of addition on
.
: ∀ (a b : Nat) (x : Int) → diff a b ≡ x → Code a b x
encode = J (λ x p → Code a b x) (+-commutative a b) encode a b x
As a finishing touch, we give Int
instances for Number
and Negative
, meaning that we can use
positive and negative integer literals to denote values of Int
.
instance
: Number Int
Number-Int .Number.Constraint _ = ⊤
Number-Int .Number.fromNat n = diff n 0
Number-Int
: Negative Int
Negative-Int .Negative.Constraint _ = ⊤
Negative-Int .Negative.fromNeg n = diff 0 n Negative-Int
Canonical representatives🔗
Initially, we note that the type of integers admits a surjection from
the type
,
given by sending each pair of naturals to their difference
.
private
: ∀ x → ∃[ (a , b) ∈ Nat × Nat ] (diff a b ≡ x)
difference-surjection (diff x y) = inc ((x , y) , refl)
difference-surjection (quot m n i) =
difference-surjection
is-prop→pathp(λ i → ∥_∥.squash {A = Σ[ (a , b) ∈ Nat × Nat ] (diff a b ≡ quot m n i)})
(inc ((m , n) , refl))
(inc ((suc m , suc n) , refl))
i
What we’ll show is that this surjection actually splits: Given an integer, we can find out what natural numbers it came from. Well, not quite: We can find a reduced representation of that difference. Namely, suppose we’re given the integer . We split by cases:
- If , then this is the same integer as ;
- If , then this is the same integer as ;
- If , then this is the same integer as .
A “canonical form” for an integer is a pair of natural numbers that
represent (under diff
) the same
integer we started with. The canonicalisation procedure does the split
we described above, appealing to a battery of three lemmas to prove the
equality.
: Int → Type
Canonical = Σ[ x ∈ Nat ] Σ[ y ∈ Nat ] (diff x y ≡ n)
Canonical n
: (n : Int) → Canonical n
canonicalise = go where
canonicalise : ∀ x y → .(x < y) → diff 0 (y - x) ≡ diff x y
lemma₁ : ∀ x y → .(y < x) → diff (x - y) 0 ≡ diff x y
lemma₂ : ∀ x y → .(x ≡ y) → diff 0 0 ≡ diff x y
lemma₃
: ∀ x y → Canonical (diff x y)
work with ≤-split x y
work x y ... | inl p = 0 , y - x , lemma₁ x y p
... | inr (inl p) = x - y , 0 , lemma₂ x y p
... | inr (inr p) = 0 , 0 , lemma₃ x y p
It remains to show that the procedure work
respects the quotient. This is a
truly gargantuan amount of work, and so it’s omitted from this page. You
can unfold it below if you dare:
No, really, it’s quite ugly.
-- I commend your bravery in unfolding this <details>! These three
-- lemmas are inductively defined on the natural numbers in a way that
-- lets us prove that the paths they return respect the Int quotient
-- without using that Int is a set (because we don't know that yet!)
(suc y) p = refl
lemma₁ zero (suc x) (suc y) p = lemma₁ x y (≤-peel p) ∙ Int.quot x y
lemma₁
(suc x) zero p = refl
lemma₂ (suc x) (suc y) p = lemma₂ x y (≤-peel p) ∙ Int.quot x y
lemma₂
= refl
lemma₃ zero zero p (suc y) p = absurd (zero≠suc p)
lemma₃ zero (suc x) zero p = absurd (suc≠zero p)
lemma₃ (suc x) (suc y) p = lemma₃ x y (suc-inj p) ∙ Int.quot x y
lemma₃
abstract
work-respects-quot: ∀ x y → PathP (λ i → Canonical (Int.quot x y i))
(work x y) (work (suc x) (suc y))
-- We split on (x, y) but also (1+x,1+y). This is obviously
-- redundant to a human, but to Agda, we must do this: there is no
-- link between these two splits.
with ≤-split x y | ≤-split (suc x) (suc y)
work-respects-quot x y ... | inl x<y | inl (s≤s x<y') = Σ-pathp refl $ Σ-pathp refl $
(∙-idl _)
commutes→square ... | inr (inl x>y) | inr (inl (s≤s x>y')) = Σ-pathp refl $ Σ-pathp refl $
(∙-idl _)
commutes→square ... | inr (inr x≡y) | inr (inr x≡y') = Σ-pathp refl $ Σ-pathp refl $
(∙-idl _)
commutes→square
-- This *barrage* of cases is to handle the cases where e.g. (x < y)
-- but (1 + x > 1 + y), which is "obviously" impossible. But Agda
-- doesn't care about what humans think is obvious.
... | inl x<y | inr (inl (s≤s x>y)) = absurd (<-asym x<y x>y)
... | inl x<y | inr (inr x≡y) = absurd (<-not-equal x<y (suc-inj x≡y))
... | inr (inl x>y) | inl (s≤s x<y) = absurd (<-asym x>y x<y)
... | inr (inr x≡y) | inl (s≤s x<y) = absurd (<-not-equal x<y x≡y)
... | inr (inl x>y) | inr (inr x≡y) = absurd (<-not-equal x>y (sym (suc-inj x≡y)))
... | inr (inr x≡y) | inr (inl (s≤s x>y)) = absurd (<-irrefl (sym x≡y) x>y)
: ∀ n → Canonical n
go (diff x y) = work x y
go (Int.quot x y i) = work-respects-quot x y i go
This immediately implies that the type of integers is a set, because it’s a retract of a set — namely !
instance abstract
: ∀ {n} → H-Level Int (2 + n)
H-Level-Int = basic-instance 2 (retract→is-hlevel 2 into from linv (hlevel 2)) where
H-Level-Int : (Nat × Nat) → Int
into (x , y) = diff x y
into
: Int → Nat × Nat
from with canonicalise x
from x ... | a , b , p = a , b
: ∀ x → into (from x) ≡ x
linv with canonicalise x
linv x ... | a , b , p = p
Recursion🔗
If we want to define a map , it suffices to give a function which respects the quotient, in the following sense:
: ∀ {ℓ} {X : Type ℓ}
Int-rec → (f : Nat → Nat → X)
→ (q : (a b : _) → f a b ≡ f (suc a) (suc b))
→ Int → X
(diff x y) = f x y
Int-rec f q (quot m n i) = q m n i Int-rec f q
However, since can be a more general space, not necessarily a set, defining a binary operation can be quite involved! It doesn’t suffice to exhibit a function from which respects the quotient separately in each argument:
: ∀ {ℓ} {B : Type ℓ}
Int-rec₂ → (f : Nat × Nat → Nat × Nat → B)
→ (pl : (a b x y : _) → f (a , b) (x , y) ≡ f (suc a , suc b) (x , y))
→ (pr : (a b x y : _) → f (a , b) (x , y) ≡ f (a , b) (suc x , suc y))
In addition, we must have that these two paths
pl
and pr
are coherent. There are two
ways of obtaining an equality
(pl
after pr
and pr
after
pl
, respectively) and these must be homotopic:
→ (square : (a b x y : _) →
(pl a b x y) (pr a b x y)
Square (pr (suc a) (suc b) x y)
(pl a b (suc x) (suc y)))
→ Int → Int → B
The type of square
says that we need the following
square of paths to commute, which says exactly that pl ∙ pr
and pr ∙ pl
are homotopic and imposes no further structure
on
1:
(diff a b) (diff x y) = f (a , b) (x , y)
Int-rec₂ f p-l p-r sq (diff a b) (quot x y i) = p-r a b x y i
Int-rec₂ f p-l p-r sq (quot a b i) (diff x y) = p-l a b x y i
Int-rec₂ f p-l p-r sq (quot a b i) (quot x y j) = sq a b x y i j Int-rec₂ f p-l p-r sq
However, when the type
we are mapping into is a set
, as is the case for the integers
themselves, the square is automatically satisfied, so we can give a
simplified recursion principle:
:
Int-rec₂-set ∀ {ℓ} {B : Type ℓ} ⦃ iss-b : H-Level B 2 ⦄
→ (f : Nat × Nat → Nat × Nat → B)
→ (pl : (a b x y : _) → f (a , b) (x , y) ≡ f (suc a , suc b) (x , y))
→ (pr : (a b x y : _) → f (a , b) (x , y) ≡ f (a , b) (suc x , suc y))
→ Int → Int → B
= Int-rec₂ f pl pr square where abstract
Int-rec₂-set ⦃ iss-b ⦄ f pl pr
square: (a b x y : Nat)
→ PathP (λ i → pl a b x y i ≡ pl a b (suc x) (suc y) i)
(pr a b x y) (pr (suc a) (suc b) x y)
= is-set→squarep (λ i j → hlevel 2) _ _ _ _ square a b x y
Furthermore, when proving propositions
of the integers, the
quotient is automatically respected, so it suffices to give the case for
diff
:
: ∀ {ℓ} {P : Int → Type ℓ}
Int-elim-prop → ((x : Int) → is-prop (P x))
→ (f : (a b : Nat) → P (diff a b))
→ (x : Int) → P x
(diff a b) = f a b
Int-elim-prop pprop f (quot m n i) =
Int-elim-prop pprop f (λ i → pprop (quot m n i)) (f m n) (f (suc m) (suc n)) i is-prop→pathp
There are also variants for binary and ternary predicates.
: ∀ {ℓ} {P : Int → Int → Type ℓ}
Int-elim₂-prop → ((x y : Int) → is-prop (P x y))
→ (f : (a b x y : Nat) → P (diff a b) (diff x y))
→ (x : Int) (y : Int) → P x y
=
Int-elim₂-prop pprop f (λ x → Π-is-hlevel 1 (pprop x))
Int-elim-prop λ a b int → Int-elim-prop (λ x → pprop (diff a b) x) (f a b) int
: ∀ {ℓ} {P : Int → Int → Int → Type ℓ}
Int-elim₃-prop → ((x y z : Int) → is-prop (P x y z))
→ (f : (a b c d e f : Nat) → P (diff a b) (diff c d) (diff e f))
→ (x : Int) (y : Int) (z : Int) → P x y z
=
Int-elim₃-prop pprop f (λ x y → Π-is-hlevel 1 (pprop x y))
Int-elim₂-prop λ a b c d int → Int-elim-prop (λ x → pprop (diff a b) (diff c d) x)
(f a b c d)
int
A third possibility for elimination is granted to us by the canonicalisation procedure: By canonicalising and looking at the resulting minuend and subtrahend2, we can split on an integer based on its sign:
Int-elim-by-sign: ∀ {ℓ} (P : Int → Type ℓ)
→ (pos : ∀ x → P (diff x 0))
→ (neg : ∀ x → P (diff 0 x))
→ P (diff 0 0)
→ ∀ x → P x
with inspect (canonicalise x)
Int-elim-by-sign P pos neg zer x ... | (zero , zero , p) , q = subst P p zer
... | (zero , suc b , p) , q = subst P p (neg (suc b))
... | (suc a , zero , p) , q = subst P p (pos (suc a))
... | (suc a , suc b , p) , q =
(canonicalise-not-both-suc x (ap fst q) (ap (fst ∘ snd) q)) absurd
This procedure is useful, for instance, for computing absolute values:
: Int → Nat
abs with by-sign x
abs x ... | posv z = z
... | negv z = suc z
_ : abs -10 ≡ 10
_ = refl
Algebra🔗
With these recursion and elimination helpers, it becomes routine to lift the algebraic operations from naturals to integers:
Successors🔗
The simplest “algebraic operation” on an integer is taking its successor. In fact, the integers are characterised by being the free type with an equivalence - that equivalence being “successor”.
: Int → Int
sucℤ (diff x y) = diff (suc x) y
sucℤ (quot m n i) = quot (suc m) n i
sucℤ
: Int → Int
predℤ (diff x y) = diff x (suc y)
predℤ (quot m n i) = quot m (suc n) i predℤ
The successor of
is
.
Similarly, the predecessor of
is
.
By the generating equality quot
, we
have that predecessor and successor are inverses, since applying both
(in either order) takes
to
.
: (x : Int) → predℤ (sucℤ x) ≡ x
pred-sucℤ (diff x y) = sym (quot x y)
pred-sucℤ (quot m n i) j = quot-diamond m n i (~ j)
pred-sucℤ
: (x : Int) → sucℤ (predℤ x) ≡ x
suc-predℤ (diff x y) = sym (quot x y)
suc-predℤ (quot m n i) j = quot-diamond m n i (~ j)
suc-predℤ
: is-equiv sucℤ
sucℤ-is-equiv = is-iso→is-equiv (iso predℤ suc-predℤ pred-sucℤ)
sucℤ-is-equiv
: is-equiv predℤ
predℤ-is-equiv = is-iso→is-equiv (iso sucℤ pred-sucℤ suc-predℤ) predℤ-is-equiv
Addition🔗
_+ℤ_ : Int → Int → Int
= diff (a + c) (b + d)
diff a b +ℤ diff c d =
diff x y +ℤ quot m n i (quot (x + m) (y + n) ∙ sym (ap₂ diff (+-sucr x m) (+-sucr y n))) i
= quot (m + x) (n + y) i
quot m n i +ℤ diff x y =
quot m n i +ℤ quot m' n' j (λ i j → hlevel 2)
is-set→squarep (λ j → quot (m + m') (n + n') j)
(quot (m + m') (n + n') ∙ sym (ap₂ diff (+-sucr m m') (+-sucr n n')))
( quot (suc (m + m')) (suc (n + n'))
(ap₂ diff (ap suc (+-sucr m m')) (ap suc (+-sucr n n'))))
∙ sym (λ j → quot (m + suc m') (n + suc n') j)
i j
Since addition of integers is (essentially!) addition of pairs of
naturals, the algebraic properties of +
on the natural numbers automatically
lift to properties about _+ℤ_
,
using the recursion helpers for props (Int-elim-prop
) and the fact that equality
of integers is a proposition.
: (x y z : Int) → x +ℤ (y +ℤ z) ≡ (x +ℤ y) +ℤ z
+ℤ-associative : (x : Int) → 0 +ℤ x ≡ x
+ℤ-zerol : (x : Int) → x +ℤ 0 ≡ x
+ℤ-zeror : (x y : Int) → x +ℤ y ≡ y +ℤ x +ℤ-commutative
See the proofs here
abstract
=
+ℤ-associative
Int-elim₃-prop(λ x y z → hlevel 1)
(λ a b c d e f → ap₂ diff (+-associative a c e) (+-associative b d f))
= Int-elim-prop (λ x → hlevel 1) (λ a b → refl)
+ℤ-zerol =
+ℤ-zeror (λ x → hlevel 1) (λ a b → ap₂ diff (+-zeror a) (+-zeror b))
Int-elim-prop =
+ℤ-commutative (λ x y → hlevel 1)
Int-elim₂-prop (λ a b c d → ap₂ diff (+-commutative a c) (+-commutative b d))
Inverses🔗
Every integer
has an additive inverse, denoted
,
which is obtained by swapping the components of the pair. Since the
definition of negate
is very
simple, it can be written conveniently without using Int-rec
:
: Int → Int
negate (diff x y) = diff y x
negate (quot m n i) = quot n m i negate
The proof that
is an additive inverse to
follows, essentially, from commutativity of addition on natural numbers,
and the fact that all zeroes are identified
.
abstract
: (x : Int) → x +ℤ negate x ≡ 0
+ℤ-inverser =
+ℤ-inverser (λ _ → hlevel 1) λ where
Int-elim-prop → diff (a + b) ⌜ b + a ⌝ ≡⟨ ap! (+-commutative b a) ⟩
a b (a + b) (a + b) ≡⟨ sym (zeroes (a + b)) ⟩
diff 0 0 ∎
diff
: (x : Int) → negate x +ℤ x ≡ 0
+ℤ-inversel =
+ℤ-inversel (λ _ → hlevel 1) λ where
Int-elim-prop → diff ⌜ b + a ⌝ (a + b) ≡⟨ ap! (+-commutative b a) ⟩
a b (a + b) (a + b) ≡⟨ sym (zeroes (a + b)) ⟩
diff 0 0 ∎ diff
Since negate
is precisely what’s
missing for Nat
to be a group, we
can turn the integers into a group. Subtraction is defined as
addition with the inverse, rather than directly on diff
:
_-ℤ_ : Int → Int → Int
= x +ℤ negate y x -ℤ y
Multiplication🔗
We now prove that the integers are a ring, i.e. that there is a multiplication operation with 1 as a left/right identity, which is associative, and additionally distributes over addition on both the left and the right. It’s also commutative — so is a commutative ring.
The definition of multiplication is slightly tricky: We use the
binomial theorem. Pretend that
is really
,
and expand that to
:
that’s our product. It remains to show that this respects the defining
equation quot
, which involves some
nasty equations (you can see them in the types of l₁
and l₂
below) — but this can be done with the semiring solver.
_*ℤ_ : Int → Int → Int
= diff (a * c + b * d) (a * d + b * c)
diff a b *ℤ diff c d = rhs₁ x y m n i
diff x y *ℤ quot m n i = rhs₂ x y m n i
quot m n i *ℤ diff x y = is-set→squarep (λ i j → hlevel 2)
quot m n i *ℤ quot x y j (rhs₂ x y m n) (rhs₁ m n x y)
(rhs₁ (suc m) (suc n) x y) (rhs₂ (suc x) (suc y) m n) i j
We omit the proofs of the arithmetic identities below since they are essentially induction + calling the semiring solver.
abstract
: ∀ x y z → x *ℤ (y *ℤ z) ≡ (x *ℤ y) *ℤ z
*ℤ-associative : ∀ x y → x *ℤ y ≡ y *ℤ x
*ℤ-commutative : ∀ x → 1 *ℤ x ≡ x
*ℤ-idl : ∀ x → x *ℤ 1 ≡ x
*ℤ-idr : ∀ x y z → x *ℤ (y +ℤ z) ≡ (x *ℤ y) +ℤ (x *ℤ z)
*ℤ-distrib-+ℤ-l : ∀ x y z → (y +ℤ z) *ℤ x ≡ (y *ℤ x) +ℤ (z *ℤ x) *ℤ-distrib-+ℤ-r