Irrelevance

Since version 2.2.8 Agda supports irrelevancy annotations. The general rule is that anything prepended by a dot (.) is marked irrelevant, which means that it will only be typechecked but never evaluated.

Note

This section is about compile-time irrelevance. See Run-time Irrelevance for the section on run-time irrelevance.

The more recent Prop serves a similar purpose and can be used to simulate irrelevance.

Motivating example

One intended use case of irrelevance is data structures with embedded proofs, like sorted lists.

  1. data __ : Nat Nat Set where
  2. zero : {n : Nat} zero n
  3. sucsuc : {m n : Nat} m n suc m suc n
  4. postulate
  5. p : 0 1
  6. p : 0 1
  7. module No-Irrelevance where
  8. data SList (bound : Nat) : Set where
  9. [] : SList bound
  10. scons : (head : Nat)
  11. (head bound)
  12. (tail : SList head)
  13. SList bound

Usually, when we define datatypes with embedded proofs we are forced to reason about the values of these proofs. For example, suppose we have two lists l₁ and l₂ with the same elements but different proofs:

  1. l : SList 1
  2. l = scons 0 p []
  3. l : SList 1
  4. l = scons 0 p []

Now suppose we want to prove that l₁ and l₂ are equal:

  1. l₁≡l : l l
  2. l₁≡l = refl

It’s not so easy! Agda gives us an error:

  1. p != p of type 0 1
  2. when checking that the expression refl has type l l

We can’t show that l₁ ≡ l₂ by refl when p₁ and p₂ are relevant. Instead, we need to reason about proofs of 0 ≤ 1.

  1. postulate
  2. proof-equality : p p

Now we can prove l₁ ≡ l₂ by rewriting with this equality:

  1. l₁≡l : l l
  2. l₁≡l rewrite proof-equality = refl

Reasoning about equality of proofs becomes annoying quickly. We would like to avoid this kind of reasoning about proofs here - in this case we only care that a proof of head ≤ bound exists, i.e. any proof suffices. We can use irrelevance annotations to tell Agda we don’t care about the values of the proofs:

  1. data SList (bound : Nat) : Set where
  2. [] : SList bound
  3. scons : (head : Nat)
  4. .(head bound) -- note the dot!
  5. (tail : SList head)
  6. SList bound

The effect of the irrelevant type in the signature of scons is that scons’s second argument is never inspected after Agda has ensured that it has the right type. The type-checker ignores irrelevant arguments when checking equality, so two lists can be equal even if they contain different proofs:

  1. l : SList 1
  2. l = scons 0 p []
  3. l : SList 1
  4. l = scons 0 p []
  5. l₁≡l : l l
  6. l₁≡l = refl

Irrelevant function types

For starters, consider irrelevant non-dependent function types:

  1. f : .A B

This type implies that f does not depend computationally on its argument.

What can be done to irrelevant arguments

Example 1. We can prove that two applications of an unknown irrelevant function to two different arguments are equal.

  1. -- an unknown function that does not use its second argument
  2. postulate
  3. f : {A B : Set} -> A -> .B -> A
  4. -- the second argument is irrelevant for equality
  5. proofIrr : {A : Set}{x y z : A} -> f x y f x z
  6. proofIrr = refl

Example 2. We can use irrelevant arguments as arguments to other irrelevant functions.

  1. id : {A B : Set} -> (.A -> B) -> .A -> B
  2. id g x = g x

Example 3. We can match on an irrelevant argument of an empty type with an absurd pattern ().

  1. data : Set where
  2. zero-not-one : .(0 1)
  3. zero-not-one ()

What can’t be done to irrelevant arguments

Example 1. You can’t use an irrelevant value in a non-irrelevant context.

  1. bad-plus : Nat .Nat Nat
  2. bad-plus n m = m + n
  1. Variable m is declared irrelevant, so it cannot be used here
  2. when checking that the expression m has type Nat

Example 2. You can’t declare the function’s return type as irrelevant.

  1. bad : Nat .Nat
  2. bad n = 1
  1. Invalid dotted expression
  2. when checking that the expression .Nat has type Set _47

Example 3. You can’t pattern match on an irrelevant value.

  1. badMatching : Nat .Nat Nat
  2. badMatching n zero = n
  3. badMatching n (suc m) = n
  1. Cannot pattern match against irrelevant argument of type Nat
  2. when checking that the pattern zero has type Nat

Example 4. We also can’t match on an irrelevant record (see Record Types).

  1. record Σ (A : Set) (B : A Set) : Set where
  2. constructor _,_
  3. field
  4. fst : A
  5. snd : B fst
  6. irrElim : {A : Set} {B : A Set} .(Σ A B) _
  7. irrElim (a , b) = ?
  1. Cannot pattern match against irrelevant argument of type Σ A B
  2. when checking that the pattern a , b has type Σ A B

If this were allowed, b would have type B a but this type is not even well-formed because a is irrelevant!

Irrelevant declarations

Postulates and functions can be marked as irrelevant by prefixing the name with a dot when the name is declared. Irrelevant definitions can only be used as arguments of functions of an irrelevant function type .A → B.

Examples:

  1. .irrFunction : Nat Nat
  2. irrFunction zero = zero
  3. irrFunction (suc n) = suc (suc (irrFunction n))
  4. postulate
  5. .assume-false : (A : Set) A

An important example is the irrelevance axiom irrAx:

  1. postulate
  2. .irrAx : {ℓ} {A : Set ℓ} -> .A -> A

This axiom is not provable inside Agda, but it is often very useful when working with irrelevance. The irrelevance axiom is a form of non-constructive choice.

Irrelevant record fields

Record fields (see Record Types) can be marked as irrelevant by prefixing their name with a dot in the definition of the record type. Projections for irrelevant fields are only created if option --irrelevant-projections is supplied (since Agda > 2.5.4).

Example 1. A record type containing pairs of numbers satisfying certain properties.

  1. record InterestingNumbers : Set where
  2. field
  3. n : Nat
  4. m : Nat
  5. .prop1 : n + m n * m + 2
  6. .prop2 : suc m n

Example 2. For any type A, we can define a ‘squashed’ version Squash A where all elements are equal.

  1. record Squash (A : Set) : Set where
  2. constructor squash
  3. field
  4. .unsquash : A
  5. open Squash
  6. .example : {A} Squash A A
  7. example x = unsquash x

Example 3. We can define the subset of x : A satisfying P x with irrelevant membership certificates.

  1. record Subset (A : Set) (P : A -> Set) : Set where
  2. constructor _#_
  3. field
  4. elem : A
  5. .certificate : P elem
  6. .certificate : {A : Set}{P : A -> Set} -> (x : Subset A P) -> P (Subset.elem x)
  7. certificate (a # p) = irrAx p

Example 4. Irrelevant projections are justified by the irrelevance axiom.

  1. .unsquash' : ∀ {A} → Squash A → A
  2. unsquash' (squash x) = irrAx x
  3. .irrAx' : ∀ {A} → .A → A
  4. irrAx' x = unsquash (squash x)

Like the irrelevance axiom, irrelevant projections cannot be reduced.

Dependent irrelevant function types

Just like non-dependent functions, we can also make dependent functions irrelevant. The basic syntax is as in the following examples:

  1. f : .(x y : A) B
  2. f : .{x y z : A} B
  3. f : .(xs {ys zs} : A) B
  4. f : x .y B
  5. f : x .{y} {z} .v B
  6. f : .{{x : A}} B

The declaration

  1. f : .(x : A) B[x]
  2. f x = t[x]

requires that x is irrelevant both in t[x] and in B[x]. This is possible if, for instance, B[x] = C x, with C : .A → Set.

Dependent irrelevance allows us to define the eliminator for the Squash type:

  1. elim-Squash : {A : Set} (P : Squash A Set)
  2. (ih : .(a : A) P (squash a))
  3. (a : Squash A) P a
  4. elim-Squash P ih (squash a) = ih a

Note that this would not type-check with (ih : (a : A) → P (squash a)).

Irrelevant instance arguments

Contrary to normal instance arguments, irrelevant instance arguments (see Instance Arguments) are not required to have a unique solution.

  1. record : Set where
  2. instance constructor tt
  3. NonZero : Nat Set
  4. NonZero zero =
  5. NonZero (suc _) =
  6. pred : (n : Nat) .{{_ : NonZero n}} Nat
  7. pred zero {{}}
  8. pred (suc n) = n
  9. find-nonzero : (n : Nat) {{x y : NonZero n}} Nat
  10. find-nonzero n = pred n