Status: DraftLicense: CC-BY-NC-SA-4.0Last edit: 2026-02-12

Type Theory

TODO

TODO Introduction

TODO Objective

  • Define a language precise enough to so that translating to a programming language is feasable, but simple enough so that we understand each others.
  • If anything is ambiguous, please send a suggestion.

:≑

x :≑ y means that x is a new name and that for now on, whenever one reads x, one may replace x by y.

If x :≑ 1, then Β« x + x Β» may be read as Β« 1 + 1 Β».

If x :≑ azerty, then Β« x + x Β» may be read as Β« azerty + azerty Β».

If x :≑ '1', then Β« x + x Β» may be read as Β« '1' + '1' Β».

≑

x ≑ y means that whenever one read x, one may replace x by y as it was already introduced before using :≑.

🞎

🞎 is a "hole" to be completed.

string : β„• β†’ String :≑ 🞎

…

… means that something is implicit and correctly typed.

string : β„• β†’ String :≑ …

X β†’ Y

Why

We want to express the fact that there is a way to start from here and get to there.

For instance, given a number, say 0, there is a unique string representation "0". So, we say: string(0) :≑ "0".

More over, if we call our numbers β„•, then for all n : β„•, we have string(n) : String.

We may see string as the name of a path, i.e. a way, to get from β„• to String.

Formation

X, Y : Type
X β†’ Y : Type

Introduction

x : X, y is a formula such that y(x) : Y
Ξ»x.y : X β†’ Y
x ↦ y :≑ Ξ»x.y

Elimination

f : X β†’ Y
x : X
f(x) : Y :≑ (Ξ»x.y)(x)

Ξ (x:X),Y(x)

Why

We want to express the fact that there is a way to start from here and get to there ; but the place we end up depends on where we start.

For instance, we might want to start from β„• and end up in String or β„• depending on where we started from.

For instance, if n is even, then f(n) :≑ n and if n is odd, then f(n) :≑ string(n).

Formation

Y : X β†’ Type
Ξ (x:X),Y(x) : Type

Introduction

Y : X β†’ Type
x : X
y formula such that y(x) : Y(x)
Ξ»x.y : Ξ (x:X),Y(x)

Elimination

f : Ξ (x:X),Y(x)
x : X
f(x) : Y(x) :≑ (Ξ»x.y)(x)

∘

Standard function composition.

∘ : (A β†’ B) (B β†’ C) β†’ A β†’ C :≑ f g a ↦ g(f(a))

β–Έ

We want to define "pipelines" of transformations that can be read left to right.

β–Έ : (A β†’ B) (B β†’ C) β†’ A β†’ C :≑ f g ↦ g ∘ f
a β–Έ f β–Έ g :≑ β–Έ(f g)(a)
2Γ— : β„• β†’ β„• :≑ n ↦ 2 Γ— n
string : β„• β†’ String :≑ …
f :≑ 2Γ— β–Έ string
f : β„• β†’ String
f(1) = "2"

X Γ— Y

Why

Given two types of instances X and Y, we might want to consider both instances at the same time.

For instance, we might want to speak about the pair Capital and Country like Paris and France at the same time.

Formation

X, Y : Type
X Γ— Y : Type

Introduction

< , > : X Y β†’ X Γ— Y

Elimination

f : X β†’ Y β†’ Z
Ο† : X Γ— Y β†’ Z :≑ <x, y> ↦ f(x)(y)

C : X Γ— Y β†’ Type
f : Ξ (x:X,y:Y),C(<x,y>)
Ο† : Ξ (p:XΓ—Y),C(p) :≑ <x, y> ↦ f(x)(y)

Ξ£(x:X),Y(x)

Why

We want instances of some type such that they represent a pair of instances where the second instance type depends on the first instance.

For instance, we might want to speak about pairs such that the second components is a string if the firt component is an even number.

Formation

X : Type
Y : X β†’ Type
Ξ£(x:X),Y(x) : Type

Introduction

< , > : x:X Y(x) β†’ Ξ£(x:X),Y(x)

Elimination

f : Ξ (x:X),Y(x) β†’ Z
Ο† : Ξ£(x:X),Y(x) β†’ Z :≑ <x, y> ↦ f(x)(y)

C : Ξ (x:X),Y(x) β†’ Type
f : Ξ (x:X) Ξ (y:Y(x)) C(<x,y>)
Ο† : Ξ (p:Ξ (x:X),Y(x)),C(p) :≑ <x, y> ↦ f(x)(y)

Magma

Magma :≑ Ξ£(A:Type)(A β†’ A β†’ A)

PointedMagma

PointedMagma :≑ Ξ£(A:Type)(A β†’ A β†’ A) Γ— A

X + Y

Why

Given two types X and Y, we might want to consider an instance that may be of one type or the other.

For instance, we might want to speak about a Capital or a Country like Paris or France.

Formation

X, Y : Type
X + Y : Type

Introduction

left : X β†’ X + Y
right : Y β†’ X + Y

Elimination

elim : (f : X β†’ Z) (g : Y β†’ Z) β†’ X + Y β†’ Z :≑
  left(x) ↦ f(x)
  right(y) ↦ g(y)

Target :≑ X + Y β†’ Type
Left : Target β†’ Type :≑ C ↦ Ξ (x:X),C((left(x)))
Right : Target β†’ Type :≑ C ↦ Ξ (y:Y),C((right(y)))
ind : C:Target Left(C) Right(C) β†’ Ξ (p:X+Y),C(p) :≑
  left(x) ↦ f(x)
  right(y) ↦ g(y)

As long as there are no ambiguities, we will use the following for conciseness:

a : A + B
b : A + B
c : A + B + C
…

β„•

Why

We want to be able to count things.

Formation

β„• : Type

Introduction

0 : β„•
succ : β„• β†’ β„•
n+1 :≑ succ(n)

Elimination

T  : Type
elim : t:T β†’ (next : β„• T β†’ T) β†’ β„• β†’ T :≑
  0 ↦ t
  n+1 ↦ next n f(n)

P : β„• β†’ Type
Init :≑ P(0)
Step :≑ β„• P(n) β†’ P(n+1)
ind : (t0 : Init) (step : Step) β†’ Ξ (n:β„•),P(n) :≑
  0 ↦ t0
  n+1 ↦ step n f(n)

πŸ™

Why

We need to have a type that has one element, to represent True.

Formation

πŸ™ : Type

Introduction

x : πŸ™

Elimination

C : Type
elim : (c:C) πŸ™ β†’ C :≑ x ↦ c

ind : (C : πŸ™ β†’ Type) (c : C(x)) β†’ Ξ (x:πŸ™), C(x) :≑ x ↦ c

𝟘

Why

We need to have a type that has no element, which is used to represent falsity.

Formation

𝟘 : Type

Introduction

Elimination

C : Type
f : 𝟘 β†’ C

C : 𝟘 β†’ Type
f : Π(x:𝟘), C(x)

Boolean

Why

We need to represents booleans i.e. a type with two elements, true or false.

Formation

Boolean : Type

Introduction

0 : Boolean
1 : Boolean

Elimination

C : Type
elim : (c0:C) (c1:C) β†’ Boolean β†’ C :≑
  0 ↦ c0
  1 ↦ c1

C : Boolean β†’ Type
ind : (c0:C(0)) (c1:C(1)) β†’ Ξ (x:Boolean), C(x) :≑
  0 ↦ c0
  1 ↦ c1

Symbol

Why

We need to represents symbols i.e. unique instances (they are only equal to themselves an nothing else).

Formation

Symbol : Type

Introduction

Symbol#mk : String β†’ Symbol
:name :≑ Symbol#mk("name")

Elimination

Behaves like a FinSet where elements are all the symbols that have been introduced so far.

Type

Following the preceding examples of types, we provide the following template to define new types:

Why

Why do we need to introduce this new type?

Formation

What is the name of that type?

Introduction

How to build instances of that type?

Elimination

How to use instances of that type?

Propositions as types

However, the type-theoretic perspective on proofs is nevertheless different in important ways. The basic principle of the logic of type theory is that a proposition is not merely true or false, but rather can be seen as the collection of all possible witnesses of its truth.

[…]

propositions are nothing but special types β€” namely, types whose elements are proofs.

[1]

English Type Theory
True 1
False 0
A and B A Γ— B
A or B A + B
If A then B A β†’ B
A if and only if B (A β†’ B) Γ— (B β†’ A)
Not A A β†’ 0
For all x : A, P(x) holds ∏(x : A),P(x)
There exists x : A such that P(x) βˆ‘(x : A),P(x)

[1]

Identity type

Why

The identity type internalizes the notion of equality (or identification) within the type theory. Given two terms a and b of the same type A, the identity type a = b is the type of evidences that a and b are equal.

Formation

A : Type
a, b : A
IdA(a, b) : U
a = b :≑ IdA(a, b)

Introduction

refl : Ξ (a:A), a = a

Elimination

C : A β†’ U
f : ∏(x,y:A)∏(p:x=y),C(x) β†’ C(y)
f(x,x,refl(x)) :≑ id(C(x))

C : ∏(x,y:A),x=y β†’ U
c : ∏(x:A),C(x,x,refl(x))
f : ∏(x,y:A), ∏(p:x=y), C(x,y,p)
f(x,x,refl(x)) :≑ c(x)

List(X)

Why

Lists of things are a common data structures, we need a way to represent them.

Formation

X : Type
List(X) : Type

Introduction

[] : List(X)
cons : X List(X) β†’ List(X)

Elimination

C : Type
elim : (c0 : C) (g : X β†’ C β†’ C) β†’ List(X) β†’ C :≑
  f [] :≑ c0
  f cons(x lst) :≑ g x f(lst)

C : List(X) β†’ Type
c0 : C([])
g : Ξ (x:X)Ξ (lst:List(X)), C(lst) β†’ C(cons(x lst))
f : Ξ (lst:List(X)), C(lst) :≑
  [] ↦ c0
  cons x lst ↦ g x lst f(lst)

map

Standard map function.

map : List(A) (A β†’ B) β†’ List(B) :≑
  as b ↦
    match as
      [] ↦ []
      cons(a as) ↦ cons(b(a) map(as b))

Bit

Why

Computers manipulates bits, let's represent them.

Formation

Bit : Type

Introduction

0 : Bit
1 : Bit

Elimination

C : Type
c0, c1 : C
f : Bit β†’ C :≑
  0 ↦ c0
  1 ↦ c1

C : Bit β†’ Type
c0 : C(0)
c1 : C(1)
f : Ξ (x:Bit), C(x) :≑
  0 ↦ c0
  1 ↦ c1

Data

Why

Computers manipulate data which are just lists of bits.

Formation

Data :≑ List(Bit)

Undefined

Why

Programmning languages have a tendency to represent undefined object, which is to say to there is something but we have no idea what that is beyond that point.

Formation

Undefined : Type

Introduction

undefined : Undefined

Elimination

Nothing

Why

How to represent the absence of a value? By using the unique instance of the Nothing type, isomorphic to πŸ™.

Formation

Nothing : Type

Introduction

nothing : Nothing

Elimination

Maybe

Why

Assume that a computers tries to map some data received from the network to some type using a given program. Assume an injective mapping, then the algorithm might fail. So, given a piece of data, the algorithm is expected to either provide an instance of some type or nothing. Let's represent that.

Formation

X : Type
Maybe(X) :≑ Nothing + X

Construction

none :≑ left(nothing)
just(x) :≑ right(x)

TODO Vector(X n)

Why

Finite lists of things that have a known length are common in programming languages, let's defined them.

Formation

X : Type
n : β„•
Vector(X n) : Type

Introduction

nil : Vector(X zero)
cons(x lst) : X Vector(X n) β†’ Vector(X n+1)

Elimination

🞎

TODO index

index(lst n) represents the nth element of lst if any, else undefined.

index : List(X 0) Nat β†’ X | Undefined
index([] n) :≑ undefined
index : List(X 1) Nat β†’ X | Undefined
index([x] 0) :≑ undefined
index([x] 1) :≑ x
Given k > 1, then index([x] k) :≑ undefined.

Given index : List(X n) Nat β†’ X | Undefined, lst+x : List(X n+1), and k : Nat, then:

if k < n+1, then index(lst k) :≑ index(lst k)
if k = n+1, then index(lst n+1) :≑ x
if k > n+1, then index(lst k) :≑ undefined

TODO String

String :≑ List(Nat)

TODO Just

X : Type
Just(X) :≑ 🞎

TODO StrictPartialOrder

StrictPartialOrder(X) :≑ 🞎

TODO Set

X : Type
= : X X β†’ Boolean
= is an equality
Set(X =) :≑
  list : List(X)
  e(i) ∈ list, e(j) ∈ list, i β‰  j, e(i) β‰  e(j)

Predicate

Predicate :≑ X β†’ Boolean

Language

cond

To express the fact if a given predicate() = true holds, then some list of instructions should be executed, we use the following syntacting form:

cond
  predicate ↦ instructions
  …
  true ↦ instructions

match

To express the fact if some piece of data x has a given form (i.e. it matches some pattern), then some list of instructions should be executed, potentially using matched patterns, we use the following syntacting form:

match x
  pattern ↦ instructions
  …
  _ ↦ instructions

If x does not match any pattern, then it is an error of the author. _ matches anything. For instance:

match maybe
  nothing ↦ "nothing"
  just(x) ↦ x

The meaning should be obvious: if maybe : Maybe(X) ≑ none, then replace the whole expression by "nothing". If maybe ≑ just(x), then replace the whole expression by x. Patterns may be extended by predicates using guards. For instance:

match x
  pattern when predicate ↦ instructions
  …

Bibliography

[1]
The Univalent Foundations Program, Homotopy type theory: Univalent foundations of mathematics, 1st ed. Princeton, NJ: Institute for Advanced Study, 2013. Available: https://hott.github.io/book/hott-online-76-ga40a78c.pdf