Equality of functions in Agda

Table of Contents

A raw Agda source file containing all the code below can be found here.

1 Introduction

A student fairly new to Agda recently asked me:

We've discussed that addition is defined by two properties;

  • zero + n = n, and
  • (suc n) + m = suc (n + m).

In Agda, can we prove a function is equal to addition if it satisfies these properties?

More generally, without knowledge of such properties, can we prove one function is equal to another?

Let's explore this topic a little bit. For simplicity, we'll restrict our attention to just unary and binary functions between natural numbers.

An important first step is deciding: what do we mean by “equal” when we say that “one function is equal to another”?

1.1 Extensional or intensional

For a better discussion of intensional versus extensional equality than I can pen, we can turn to Paul Taylor's “Practical foundations of mathematics”. .

For Leonhard Euler (1748) and most mathematicians up to the end of the nineteenth century, a function was an expression formed using the arithmetical operations and transcendental operations such as log. The modern infor-matician would take a similar view, but would be more precise about the method of formation (algorithm). Two such functions are equal if this can be shown from the laws they are known to obey.

However, during the twentieth century mathematics students have been taught that a function is a set of input-output pairs. The only condition is that for each input value there exists, somehow, an output value, which is unique. This is the graph of the function: plotting output values in the y-direction against arguments along the x-axis, forgetting the algorithm. Now two functions are equal if they have the same output value for each input. (This definition was proposed by Peter Lejeune Dirichlet in 1829, but until 1870 it was thought to be far too general to be useful).

These definitions capture the intension and the effect (extension) of a function.

So we must be careful which notion of equality we are using during our discussion below. We will demonstrate that we cannot in general prove functions are intensionally equal in Agda.

2 Equality in Agda

We will use the Agda standard library definitions of the natural numbers, addition, and propositional equality in this discussion, along with equational reasoning for proofs.

open import Data.Nat using (ℕ ; zero ; suc ; _+_)

import Relation.Binary.PropositionalEquality as Eq
open Eq using (_≡_ ; refl)
open Eq.≡-Reasoning using (begin_ ; _≡⟨⟩_ ; _≡__ ; _∎)

2.1 Mechanics of propositional equality

Propositional equality is defined by just one constructor (rule), refl. refl tells us that any entity is equal to itself. So, for instance, zero ≡ zero and suc (suc zero) ≡ suc (suc zero).

Given this definition, it might be a bit surprising that, for instance, zero + zero ≡ zero. Such equalities hold because Agda normalises terms during typechecking. By the definition of addition, zero + zero normalises to zero, and so zero + zero ≡ zero normalises to zero ≡ zero, which is inhabited by refl.

2.2 Intensional equality

Propositional equality is an intensional equality, as noted in the comment at the top of Relation.Binary.PropositionalEquality:

------------------------------------------------------------------------
-- The Agda standard library
--
-- Propositional (intensional) equality
------------------------------------------------------------------------

Indeed, propositional equality is intentional; two entities are only propositionally equal if their normal forms are defined the same way (from the constructors of their type).

Hence, we will say that say that two (Agda) functions f and g are intensionally equal if we can prove that f ≡ g.

2.3 Extensional equality

We will use propositional (intensional) equality in order to define extensional equality in Agda.

We will call two Agda functions f and g of type A → B extensionally equal if we can prove (x : A) → f x ≡ g x. Note we could write a explicitly, as in ∀ (x : A) → f x ≡ g x, but it is not necessary and we will omit it.

Since we are dealing with binary (curried) functions on the naturals, extensional equality below will specifically mean (m n : ℕ) → f m n ≡ g m n.

3 Setup

Here we make use of the following definitions from the standard library.

open import Data.Nat using (ℕ ; zero ; suc ; _+_)
open import Data.Nat.Properties using (+-identityʳ)

open import Relation.Nullary using (¬_)
import Relation.Binary.PropositionalEquality as Eq
open Eq using (_≡_ ; refl ; sym ; cong)
open Eq.≡-Reasoning using (begin_ ; _≡⟨⟩_ ; _≡__ ; _∎)

For the purposes of this thought experiment, we will work with this Agda record.

record PseudoAddition : Set where
  field
    _⊕_ : ℕ → ℕ → ℕ
    z⊕n≡n : (n : ℕ) → zero ⊕ n ≡ n
    [suc-m]⊕n≡suc-[m⊕n] : (m n : ℕ) → (suc m) ⊕ n ≡ suc (m ⊕ n)

We call the record PseudoAddition, since we are claiming that the operator is, in fact, addition, even though we do not have its definition. Practically speaking, this means we have as assumptions the existence of this operator and the two properties describing its behaviour.

We could equivalently have made the fields parameters to this Agda module; in Agda, records define a module, so there's not much difference.

4 ⊕ equals +… extensionally

It's relatively simple to prove that our assumed operator is extensionally equal to addition, given the properties zero ⊕ n ≡ n and m ⊕ n ≡ m + n. Enough so that we leave this as exercise to the reader. Make use of cong in the induction step; you'll probably need just two steps.

record PseudoAddition : Set where
  field
    _⊕_ : ℕ → ℕ → ℕ
    z⊕n≡n : (n : ℕ) → zero ⊕ n ≡ n
    [suc-m]⊕n≡suc-[m⊕n] : (m n : ℕ)(suc m) ⊕ n ≡ suc (m ⊕ n)
  m⊕n≡m+n : (m n : ℕ) →  m ⊕ n ≡ m + n
  m⊕n≡m+n zero n = z⊕n≡n n
  m⊕n≡m+n (suc m) n =
    begin
      (suc m ⊕ n)
    ≡⟨ ??? ⟩
      suc (m + n)
    ∎

5 An example

Let's take a short break here to see an actual use of our record; we can define an operator similar, but not quite equal to addition, and by providing proofs that it satisfies the two properties defining plus, the record provides us with a proof that our operator is extensionally equal to addition. (Note the connection between Agda records and Haskell-style typeclasses).

_+₀_ : ℕ → ℕ → ℕ
m +₀ n = m + n + 0

+₀-is-PseudoAddition : PseudoAddition
+₀-is-PseudoAddition =
  record { _⊕_ = _+₀_
         ; z⊕n≡n = +-identityʳ
         ; [suc-m]⊕n≡suc-[m⊕n] = λ _ _ → refl }

m+₀n≡m+n : (m n : ℕ) → m +₀ n ≡ m + n
m+₀n≡m+n = PseudoAddition.m⊕n≡m+n +₀-is-PseudoAddition

6 ⊕ does not equal + intensionally

So, we can show prove that our operation is extensionally equal to addition. However, if we try to prove

  ⊕≡+ : _⊕_ ≡ _+_
  ⊕≡+ = ???

we will find we cannot. (Note we cannot disprove it either).

In fact, even if we know the definition of the operator, and that definition is exactly the same as that of addition, as in

_plus_ : ℕ → ℕ → ℕ
zero  plus n = n
suc m plus n = suc (m plus n)

we still cannot prove it is intensionally equal to addition.

Why not!?

A post by Peter Selinger on the Agda mailing list sheds some light on the situation. Specifically, he sums up the situation as

…function equality in Agda is definitional equality, i.e., two functions are equal if and only if they are internally represented as pointers to the same closure.

6.1 Well, actually…

There is small a gotcha here; this statement applies to functions defined by pattern matching. Functions not defined by pattern matching are simply abbreviations, and those abbreviations are expanded during equality checks. See the later post in the same thread by Andreas Abel.

So, this code will typecheck.

_really-plus_ : ℕ → ℕ → ℕ
m really-plus n = m + n

really-plus≡+ : _really-plus_ ≡ _+_
really-plus≡+ = refl

7 The general case

Given what we've seen above with and addition, we can conclude this about checking equality of functions in Agda:

  • We can, given enough information, prove that two functions are extensionally equal to each other (x : A → f x ≡ g x).
  • Only in special cases can we say that two functions are intensionally equal (f ≡ g).
    • Specifically, when both are actually the same (f ≡ f) or when one is an abbreviation of the other (defined without pattern matching).

8 Inequality

To round out this discussion, let's consider the negative case; when can we say, in Agda, that two functions are not equal? And can we prove they are not intensionally equal, or only that they are not extensionally equal?

Actually, we can do both. And in general, whenever we prove that two functions are not extensionally equal, we can prove that they are not intensionally equal. We prove this easily as a lemma.

disagree : {A B : Set}{f g : A → B}(witness : A) → ¬ (f witness ≡ g witness)
         → ¬ (f ≡ g)
disagree witness fw≢gw refl = fw≢gw refl

Now let us examine this specific instance dealing with pseudo-addition by considering two records, covering the two cases:

  1. does not satisfy zero ⊕ n ≡ n, for some witness n, and
  2. does not satisfy (suc m) ⊕ n ≡ suc (m ⊕ n), for some witnesses m and n.

In either case, we can prove by contradiction both

  • that is not extensionally equal to addition, and
  • that is not intensionally equal to addition.
    • Note: we prove these cases manually, not using disagree, because our assumptions reason only about , so are not of the correct shape (¬ m ⊕ n ≡ m + n) for disagree.

First, let's assume that zero ⊕ n is not n.

record NotAddition-zero : Set where
  field
    _⊕_ : ℕ → ℕ → ℕ
    nʷ : ℕ   -- witness
    ¬z⊕nʷ≡nʷ : ¬ (zero ⊕ nʷ ≡ nʷ)

  ¬a⊕b≡a+b : ¬ ((a b : ℕ) → a ⊕ b ≡ a + b)
  ¬a⊕b≡a+b a⊕b≡a+b = ¬z⊕nʷ≡nʷ (a⊕b≡a+b zero nʷ)

  ¬⊕≡+ : ¬ _⊕_ ≡ _+_
  ¬⊕≡+ ⊕≡+ = ¬z⊕nʷ≡nʷ z⊕n≡n
    where
      z⊕n≡n : zero ⊕ nʷ ≡ nʷ
      z⊕n≡n =
        begin
          zero ⊕ nʷ
        ≡ cong (λ op → op zero nʷ) ⊕≡+ 
          zero + nʷ
        ≡⟨⟩
          nʷ
        ∎

Now, let us assume that for some m and n, (suc m) ⊕ n is not suc (m ⊕ n).

record NotAddition-suc : Set where
  field
    _⊕_ : ℕ → ℕ → ℕ
    mʷ nʷ : ℕ   -- witnesses
    ¬[suc-mʷ]⊕nʷ≡suc-[mʷ⊕nʷ] : ¬ ((suc mʷ) ⊕ nʷ ≡ suc (mʷ ⊕ nʷ))

  ¬a⊕b≡a+b : ¬ ((a b : ℕ) → a ⊕ b ≡ a + b)
  ¬a⊕b≡a+b a⊕b≡a+b = ¬[suc-mʷ]⊕nʷ≡suc-[mʷ⊕nʷ] [suc-mʷ]⊕nʷ≡suc-[mʷ⊕nʷ]
    where
      [suc-mʷ]⊕nʷ≡suc-[mʷ⊕nʷ] : (suc mʷ) ⊕ nʷ ≡ suc (mʷ ⊕ nʷ)
      [suc-mʷ]⊕nʷ≡suc-[mʷ⊕nʷ] =
        begin
          (suc mʷ) ⊕ nʷ
        ≡ a⊕b≡a+b (suc mʷ)
          (suc mʷ) + nʷ
        ≡⟨⟩
          suc (mʷ + nʷ) cong suc (sym (a⊕b≡a+b mʷ nʷ)) 
          suc (mʷ ⊕ nʷ)
        ∎

  ¬⊕≡+ : ¬ _⊕_ ≡ _+_
  ¬⊕≡+ ⊕≡+ = ¬[suc-mʷ]⊕nʷ≡suc-[mʷ⊕nʷ] [suc-mʷ]⊕nʷ≡suc-[mʷ⊕nʷ]
    where
      [suc-mʷ]⊕nʷ≡suc-[mʷ⊕nʷ] : (suc mʷ) ⊕ nʷ ≡ suc (mʷ ⊕ nʷ)
      [suc-mʷ]⊕nʷ≡suc-[mʷ⊕nʷ] =
        begin
          (suc mʷ) ⊕ nʷ
        ≡⟨⟩
          suc mʷ ⊕ nʷ
        ≡ cong (λ op → op (suc mʷ)) ⊕≡+ 
          suc mʷ + nʷ
        ≡⟨⟩
          suc (mʷ + nʷ) cong (λ op → suc (op mʷ nʷ)) (sym ⊕≡+) 
          suc (mʷ ⊕ nʷ)⟨⟩
          suc (mʷ ⊕ nʷ)

9 Further reading

The chapter on isomorphisms in Programming Language Foundations in Agda includes a discussion on adding the axiom of extensionality to Agda.

10 About this document

This document was written as a literate Agda file using Org mode, from which it was exported to (potentially several) formats. The original Org source code, with Agda code between the #+begin_src agda2 and #+end_src blocks, can be found here, along with the pure Agda code tangled from this file.

Author: Mark Armstrong markarmstrong.jpg

Contact: markparmstrong@gmail.com

Original date: January 21st, 2020

Last updated: 2021-01-27 Wed 02:13

Created using Emacs 27.0.90 (Org mode 9.4.4)

Validate