2
votes

Suppose I have a transitive relation ~with two endomaps f and g. Assuming f and g agree everywhere and f a ~ f b ~ f c then there are two ways to show g a ~ g c: transform each f into a g by the given equality then apply transitivity, or apply transitivity then transform along the equality. Are the resulting proofs identical? Apparently so,

open import Relation.Binary.PropositionalEquality

postulate A : Set
postulate _~_  : A → A → Set
postulate _⟨~~⟩_ : ∀{a b c} → a ~ b → b ~ c → a ~ c
postulate f g : A → A

subst-dist : ∀{a b c}{ef : f a ~ f b}{psf : f b ~ f c} → (eq : ∀ {z} → f z ≡ g z)
     → 
         subst₂ _~_ eq eq ef ⟨~~⟩ subst₂ _~_ eq eq psf 
       ≡ subst₂ _~_ eq eq (ef ⟨~~⟩ psf)

subst-dist {a} {b} {c} {ef} {psf} eq rewrite eq {a} | eq {b} | eq {c} = refl

I just recently learned about the rewrite keyword and thought it might help here; apparently it does. However, I honestly do not understand what is going on here. I've used rewrite other times, with comprehension. However, all these substs are confusing me.

I'd like to know

  • if is there a simplier way to obtain subst-dist? Maybe something similar in the libraries?
  • what is going on with this particular usage of rewrite
  • an alternate proof of subst-dist without using rewrite (most important)
  • is there another way to obtain g a ~ g c without using subst?
  • what are some of the downsides of using heterogeneous equality, it doesn't seem like most people are fond of it. (also important)

Any help is appreciated.

1

1 Answers

5
votes

rewrite is just a sugared with, which is just sugared "top-level" pattern matching. See in Agda’s documentation.

what are some of the downsides of using heterogeneous equality, it doesn't seem like most people are fond of it. (also important)

This is OK

types-equal : ∀ {α} {A B : Set α} {x : A} {y : B} -> x ≅ y -> A ≡ B
types-equal refl = refl

this is OK as well

A-is-Bool : {A : Set} {x : A} -> x ≅ true -> A ≡ Bool
A-is-Bool refl = refl

This is an error

fail : ∀ {n m} {i : Fin n} {j : Fin m} -> i ≅ j -> n ≡ m
fail refl = {!!}

-- n != m of type ℕ
-- when checking that the pattern refl has type i ≅ j

because Fin n ≡ Fin m doesn't immediately imply n ≡ m (you can make it so by enabling --injective-type-constructors, but that makes Agda anti-classical) (Fin n ≡ Fin m -> n ≡ m is provable though).

Originally Agda permitted to pattern match on x ≅ y when x and y have non-unifiable types, but that allows to write weird things like (quoting from this thread)

  P : Set -> Set
  P S = Σ S (\s → s ≅ true)

  pbool : P Bool
  pbool = true , refl

  ¬pfin : ¬ P (Fin 2)
  ¬pfin ( zero , () )
  ¬pfin ( suc zero , () )
  ¬pfin ( suc (suc ()) , () )

  tada : ¬ (Bool ≡ Fin 2)
  tada eq = ⊥-elim ( ¬pfin (subst (\ S → P S) eq pbool ) )

Saizan or maybe it's just ignoring the types and comparing the constructor names?

pigworker Saizan: that's exactly what I think is happening

Andread Abel:

  • If I slighly modify the code, I can prove Bool unequal Bool2, where true2, false2 : Bool2 (see file ..22.agda)
  • However, if I rename the constructors to true, false : Bool2, then suddenly I cannot prove that Bool is unequal to Bool2 anymore (see other file). So, at the moment Agda2 compares apples and oranges in certain situations. ;-)

So in order to pattern match on i ≅ j, where i : Fin n, j : Fin m, you first need to unify n with m

OK : ∀ {n m} {i : Fin n} {j : Fin m} -> n ≡ m -> i ≅ j -> ...
OK refl refl = ...

That's the main drawback of heteregeneous equality: you need to provide proofs of equality of indices everywhere. Usual cong and subst are non-indexed, so you also have to provide indexed versions of them (or use even more annoying cong₂ and subst₂).

There is no such problem with "heteroindexed" (I don't know if it has a proper name) equality

data [_]_≅_ {ι α} {I : Set ι} {i} (A : I -> Set α) (x : A i) : ∀ {j} -> A j -> Set where
  refl : [ A ] x ≅ x

e.g.

OK : ∀ {n m} {i : Fin n} {j : Fin m} -> [ Fin ] i ≅ j -> n ≡ m
OK refl = refl

More generally, whenever you have x : A i, y : A j, p : [ A ] x ≅ y, you can pattern match on p and j will be unified with i, so you don't need to carry an additional proof of n ≡ m.

Heterogeneous equality, as it presented in Agda, is also inconsistent with the univalence axiom.

EDIT

Pattern matching on x : A, y : B, x ≅ y is equal to pattern matching on A ≡ B and then changing every y in a context to x. So when you write

fail : ∀ {n m} {i : Fin n} {j : Fin m} -> i ≅ j -> n ≡ m
fail refl = {!!}

it's the same as

fail' : ∀ {n m} {i : Fin n} {j : Fin m} -> Fin n ≡ Fin m -> i ≅ j -> n ≡ m
fail' refl refl = {!!}

but you can't pattern match on Fin n ≡ Fin m

fail-coerce : ∀ {n m} -> Fin n ≡ Fin m -> Fin n -> Fin m
fail-coerce refl = {!!}

-- n != m of type ℕ
-- when checking that the pattern refl has type Fin n ≡ Fin m

like you cannot pattern match on

fail'' : ∀ {n m} -> Nat.pred n ≡ Nat.pred m -> n ≡ m
fail'' refl = {!!}

-- n != m of type ℕ
-- when checking that the pattern refl has type Nat.pred n ≡ Nat.pred m

In general

f-inj : ∀ {n m} -> f n ≡ f m -> ...
f-inj refl = ...

works only if f is obviously injective. I.e. if f is a series of constructors (e.g. suc (suc n) ≡ suc (suc m)) or computes to it (e.g. 2 + n ≡ 2 + m). Type constructors (which Fin is) are not injective because that would make Agda anti-classical, so you cannot pattern on Fin n ≡ Fin m unless you enable --injective-type-constructors.

Indices unify for

data [_]_≅_ {ι α} {I : Set ι} {i} (A : I -> Set α) (x : A i) : ∀ {j} -> A j -> Set where
  refl : [ A ] x ≅ x

because you don't try to unify A i with A j, but instead explicitly carry indices in the type of [_]_≅_, which make them available for unification. When indices are unified, both types become the same A i and it's possible to proceed like with propositional equality.

EDIT

One another problem with heterogeneous equality is that it's not fully heterogeneous: in x : A, y : B, x ≅ y A and B must be in the same universe. The treatment of universe levels in data definitions has been changed recently and now we can define fully heterogeneous equality:

data _≅_ {α} {A : Set α} (x : A) : ∀ {β} {B : Set β} -> B -> Set where
  refl : x ≅ x

But this doesn't work

levels-equal : ∀ {α β} -> Set α ≅ Set β -> α ≅ β
levels-equal refl = refl

-- Refuse to solve heterogeneous constraint Set α : Set (suc α) =?=
-- Set β : Set (suc β)

because Agda doesn't think suc is injective

suc-inj : {α β : Level} -> suc α ≅ suc β -> α ≅ β
suc-inj refl = refl

-- α != β of type Level
-- when checking that the pattern refl has type suc α ≅ suc β

If we postulate it, then we can prove levels-equal:

hcong : ∀ {α β δ} {A : Set α} {B : Set β} {D : Set δ} {x : A} {y : B}
      -> (f : ∀ {γ} {C : Set γ} -> C -> D) -> x ≅ y -> f x ≅ f y
hcong f refl = refl

levelOf : ∀ {α} {A : Set α} -> A -> Level
levelOf {α} _ = α

postulate
  suc-inj : {α β : Level} -> suc α ≅ suc β -> α ≅ β

levels-equal : ∀ {α β} -> Set α ≅ Set β -> α ≅ β
levels-equal p = suc-inj (suc-inj (hcong levelOf p))