A: Type vs A <: Any

Types in Lean are first-class objects, meaning that they can be treated as regular values: they have a type, they can be stored, passed around, returned from functions. So A: Type means that A is a type.

Since every expression has a type, it is natural to ask: what is the type of Type? Turns out there is a type called Type 1 that has Type as an inhabitant.

Type : Type 1

and a type Type 2 that has Type 1 as an inhabitant.

Type 1 : Type 2

and so on.

In fact, Type is just an alias for Type 0 and we have an infinite tower of types

...
Type 2 : Type 3
Type 1 : Type 2
Type 0 : Type 1

It’s types all the way up!

Graphically:

universe hierarchy

In this diagram the boxes at the top are types, and their inhabitants (also types) are displayed inside. The inhabitants that are not types (such as regular numbers) are displayed at the bottom, connected by broken lines.

Each concrete number i above is called a universe level.

Note that list Nat has type Type 0, whereas list Type has type Type 1.

If we want to declare type variables of any universe we can use the syntax {A: Type _} like so:

Lean 4:

def length' {A: Type _}: List A -> Nat
  | []     =>  0
  | _ :: t =>  1 + length' t 

In Lean, propositions are types that sit at the bottom of the universe hierarchy:

...
Type 2 : Type 3
Type 1 : Type 2
Type 0 : Type 1
Nat    : Type 0 
Prop   : Type 0
2 = 3  : Prop
2 = 2  : Prop
rfl 2  : 2 = 2
...

Graphically:

universe hierarchy props

You can see that Type 0 has Prop as an inhabitant (alongside with Nat, and others not shown). Prop’s own inhabitants (such as 2 = 2, 2 = 3) are types as well.


Profile picture

Personal blog of Juan Pablo Romero Méndez.