Amélia Liao
Open source, combination wiki (à la nLab) + Agda library.
contains basic results about identity.
Very basic results in algebra + synthetic homotopy theory (e.g. HoTT ch. 8)
This talk is a module in the 1Lab!
Constructive but impredicative (propositional resizing); homotopical features used freely
Type classes for automation, but only of properties; equipping a type with structure (e.g. algebraic) is always explicit.
Playground for new Agda features, e.g. opaque
, OVERLAPPING
instances
HoTT simplifies Martin-Löf type theory by giving universes a nice universal property (univalence)
Cubical type theory complicates type theory to make this
compute (uaβ
)
Lots of work has gone into usability of traditional proof assistants; but what about higher dimensional proof assistants?
When are functions f,g:A→B identical?
Expected answer: whenever f(x)≡g(x) for all x;
Actual answer: 🤷♀️!
Extensionality is independent of MLTT. E.g. antifunext.
But it’s for doing maths. Our solution: the interval, paths.
A type I
, with
endpoints i0
, i1
, which (co)represents
identifications.
An identification p:x≡Ay is an element i:I,p(i):A that satisfies p(i0)=x and p(i1)=y.
In Agda: path lambdas and path application.
We can even explain things like pattern matching on HITs:
example : (p : S¹) → S¹
example base = base
example (loop i) = {! !}
-- Goal: S¹
-- i = i0 ⊢ base
-- i = i1 ⊢ base
Introducing path abstractions does a number on inference:
Agda generates constraints:
_x h x y (i = i0) = x _y h x y (i = i1) = y
Neither of these is fully determined!
Both metas go
unsolved.
Need a nice interface for extensional equality.
Ideally: the identity type x≡Ay computes to something simpler, based on the structure of A.
Observational type theory with strict propositions…
Higher observational type theory…
Key example: group homomorphisms
f,g:G→H.
These are pairs, so
f≡g
computes to a pair type.
But the second component — the proof that
f
is a homomorphism — is irrelevant!
Without a sort of strict propositions, the system can’t “see” this.
So “primitive higher-OTT” will still need a helper, to fill in the
trivial second component.
Surprisingly, we can use type classes!
record Extensional (A : Type) : Type (lsuc lzero) where
field
_≈_ : A → A → Type
to-path : ∀ {x y : A} → x ≈ y → x ≡ y
We have an instance for each type former we want to compute:
We can test that this works by asking Agda to check ext
at various types:
_ : {A B : Type} {f g : A → B} → (∀ x → f x ≡ g x) → f ≡ g
_ = ext
_ : {A B C : Type} {f g : A → B → C} → (∀ x y → f x y ≡ g x y) → f ≡ g
_ = ext
_ : {A : Type} {x y : A} → x ≡ y → x ≡ y
_ = ext
A benefit: type class solving isn’t a real function. Can be unstable under substitution!
instance
ext-uncurry : ⦃ Extensional (A → B → C) ⦄ → Extensional (A × B → C)
ext-uncurry ⦃ e ⦄ = record
{ _≈_ = λ f g → e ._≈_ (curry f) (curry g)
; to-path = λ h i (x , y) → e .to-path h i x y
}
{-# OVERLAPPING ext-uncurry #-}
The resulting relation has three arguments, rather than two:
The real implementation handles maps of algebraic structures, e.g. groups,
maps from certain higher inductive types, e.g. generic set-quotients, or abelian group homomorphisms from a tensor product,
_ : {A B C : Abelian-group lzero} {f g : Ab.Hom (A ⊗ B) C}
→ ((x : ⌞ A ⌟) (y : ⌞ B ⌟) → f # (x , y) ≡ g # (x , y))
→ f ≡ g
_ = ext
and also: natural transformations, maps in slice categories, in comma categories, in categories of elements, in wide and full subcategories, in categories of monad algebras, type-theoretic equivalences and embeddings, monotone maps, &c., &c.
Same setup: when are types
A,B:Type
identical?
Ideal answer: when they are indistinguishable.
What distinguishes types?
Take e.g. N×String vs. String×N:
In base MLTT (or even e.g. Lean): no proof that they’re distinct
… because if you give me any property that holds of one, I can modify your proof to show it holds of the other (by hand)
… so these types should be identical!
The actual answer: 🤷♀️
Univalence (and equality reflection!) says
they’re the same; (set-level) OTT says they’re different; everyone else
is undecided
Univalence says (A≡B) is equivalent to (A≃B):
Just as much control over p:A≡B as we would have over f:A≃B;
Funny looking consequences: e.g. Z≃N, and “Z is a group”, so transport lets us conclude “N is a group.”
Implications for library design?
The traditional approach for doing algebra in a proof assistant is by letting the elaborator fill in the structure
Technically different approaches, but, in effect, you can say x,y:Z and write “x+y” to mean “the addition on Z”
From a user’s POV, great! Can write maths “as on paper” and the system does “the right thing”.
But it’s actually pretty funny if you think about it:
“Additive” monoid vs “multiplicative” monoid; the same algebraic structure, but the entire hierarchy is duplicated
Type synonyms like OrderDual
:
Pod
is definitionally
P,
but type class search doesn’t respect that
Requires careful setup to avoid diamonds
Mostly sensible for concrete types, but “the” order on X×Y is a choice!
In effect, mathematical vernacular makes explicit the stuff, leaves the structure implicit, and hardly ever mentions the properties.
Our approach is to always bundle types with the structure under consideration. Three-layered approach:
record is-ring {A : Type} (1r : A) (_*_ _+_ : A → A → A) : Type where
-- _*_ is a monoid, _+_ is an abelian group, distributivity
record Ring-on (A : Type) : Type where
field
1r : A
_*_ _+_ : A → A → A
has-is-ring : is-ring 1r _*_ _+_
Ring : Type₁
Ring = Σ[ T ∈ Type ] Ring-on T -- almost
Similarly is-group
/Group-on
/Group
, is-monoid
/Monoid-on
/Monoid
, etc.
This layered approach still requires a bit of choosing. Categorically, the guidelines are:
Type-theoretically, we can say:
These sometimes conflict: once we fix the multiplication, a monoid has…
Simplifying a bit, a monoid M is a tuple (M0,∗,1,λ,ρ,α) consisting of
When are two monoids the same?
Let’s make it a bit more formal: we define the property, parametrised over the stuff and the structure;
is-monoid : (M : Type) → (M → M → M) → M → Type
is-monoid M m u =
is-set M -- required so is-monoid is a property
× (∀ x → m u x ≡ x) -- left identity
× (∀ x → m x u ≡ x) -- right identity
× (∀ x y z → m x (m y z) ≡ m (m x y) z) -- associativity
We’ll need that it’s an actual property:
Then we sum it all:
Monoid : Type₁
Monoid =
Σ[ M ∈ Type ] -- stuff
Σ[ m ∈ (M → M → M) ] Σ[ u ∈ M ] -- structure
(is-monoid M m u) -- property
Completely formally, a Monoid
has 7 fields, but we’ve shown that 4 don’t matter.
Then we can compute the identity type: If we have monoids M=(M0,m,u,α) and N=(N0,n,v,β), what is M≡N?
Step 1: an identity of tuples is a tuple of identities, and identity
in is-monoid
is trivial;
Step 2: we can simplify the PathP
s to talk about transport
instead:
Step₂ : Type₁
Step₂ = Σ[ p ∈ M₀ ≡ N₀ ]
( (∀ x y → transport p (m x y) ≡ n (transport p x) (transport p y))
× transport p u ≡ v
)
step₂ : Step₂ → Step₁
step₂ (p , q , r) = p , q' , to-pathp r where
-- q' actually pretty complicated...
q' = funext-dep λ α → funext-dep λ β →
to-pathp (q _ _ ∙ ap₂ n (from-pathp α) (from-pathp β))
Step 3: we know what identity of types is!
Step₃ : Type
Step₃ = Σ[ p ∈ M₀ ≃ N₀ ] -- equivalence!
( (∀ x y → p .fst (m x y) ≡ n (p .fst x) (p .fst y))
× p .fst u ≡ v
)
Takes a bit of massaging…
step₃ : Step₃ → Step₂
step₃ (p , q , r) = ua p , q' , r' where
r' = transport (ua p) u ≡⟨ uaβ p u ⟩
p .fst u ≡⟨ r ⟩
v ∎
q' : ∀ x y → _
q' x y =
transport (ua p) (m x y) ≡⟨ uaβ p (m x y) ⟩
p .fst (m x y) ≡⟨ q x y ⟩
n (p .fst x) (p .fst y) ≡⟨ ap₂ n (sym (uaβ p x)) (sym (uaβ p y)) ⟩
n (transport (ua p) x) (transport (ua p) y) ∎
The conclusion: identity in the type Monoid
is exactly¹ monoid
isomorphism!
While it’s great that the theory works, the proofs are pretty
annoying.
But they’re very mechanical — incremental!
Our solution: we can make the three-layer approach from before into an actual theorem, using displayed categories.
An alternative presentation of the data of a category D equipped with a “projection” functor π:D→C; just more “native” to type theory.
The idea: turn fibers into families.
If we start with a displayed category EC, we can put together a category with objects ∑x:COb[x] — the total category ∫E.
Similarly, each x:C gives a category E∗(x) with objects Ob[x] and maps Hom[id](−,−) — the fibre over x.
Properties of the functor π:∫E→C map very nicely to properties of E!
π is… | E satisfies… |
---|---|
faithful | each Hom[−](−,−) is a proposition |
full | each
Hom[−](−,−)
is inhabited |
fully faithful | each Hom[−](−,−) is contractible |
amnestic | each E∗(x) univalent, and so is C |
That last row is important!
If we start with a functor, then Ob[x] is the type ∑y:Dπ(y)≅x; the “fibre” of π over x.
The maps over are more complicated:
Accordingly, the structure is pretty annoying:
We can present a concrete, univalent category as a displayed category.
These data amount to:
A type family of structures F:Type→Type;
A proposition Hom[f](S,T), given f:A→B, S:F(A), T:F(B).
This type defines what it means for f to be a “F-structure-preserving map from S to T”
Proofs that (2) contains the identities and is closed under composition;
For univalence: a proof that if id is a structure preserving map S→T (and also T→S), then S=T.
We start by defining the property, parametrised by the structure:
record is-semigroup {A : Type ℓ} (_⋆_ : A → A → A) : Type ℓ where
field
has-is-set : is-set A
assoc : ∀ {x y z} → x ⋆ (y ⋆ z) ≡ (x ⋆ y) ⋆ z
We can derive that this is a property pretty mechanically:
Then we define the structure:
record Semigroup-on (A : Type ℓ) : Type ℓ where
field
_⋆_ : A → A → A
has-is-semigroup : is-semigroup _⋆_
open is-semigroup has-is-semigroup public
… and what it means for functions to preserve it:
record is-semigroup-hom (f : A → B) (S : Semigroup-on A) (T : Semigroup-on B)
: Type (level-of A ⊔ level-of B) where
private
module S = Semigroup-on S
module T = Semigroup-on T
field
pres-⋆ : ∀ x y → f (x S.⋆ y) ≡ f x T.⋆ f y
private
unquoteDecl eqv = declare-record-iso eqv (quote Semigroup-on)
unquoteDecl eqv' = declare-record-iso eqv' (quote is-semigroup-hom)
instance
H-Level-is-semigroup : ∀ {n} {s : A → A → A} → H-Level (is-semigroup s) (suc n)
H-Level-is-semigroup = prop-instance is-semigroup-is-prop
H-Level-is-semigroup-hom
: ∀ {n} {f : A → B} {S : Semigroup-on A} {T : Semigroup-on B}
→ H-Level (is-semigroup-hom f S T) (suc n)
H-Level-is-semigroup-hom {T = T} = prop-instance (Iso→is-hlevel 1 eqv' (hlevel 1))
where instance _ = hlevel-instance {n = 2} (T .Semigroup-on.has-is-set)
open is-semigroup-hom
We can then fill in the four bullet points.
F
is Semigroup-on
,
Hom[−](−,−)
is is-semigroup-hom
,
Semigroup-structure : ∀ {ℓ} → Thin-structure ℓ Semigroup-on
Semigroup-structure .is-hom f S T = el! (is-semigroup-hom f S T)
… identities and composites are respected …
Semigroup-structure .id-is-hom .pres-⋆ x y = refl
Semigroup-structure .∘-is-hom f g α β .pres-⋆ x y =
ap f (β .pres-⋆ x y) ∙ α .pres-⋆ _ _
and, finally, the univalence condition:
Cubical type theory works in practice, but handling the raw primitives bites
Univalence challenges us to reconsider the notion of structure