Search code examples
equalityagdalifting

Equality in Agda - irrelevant arguments


I have a dependant type that consists of a value plus some proofs about its properties. Naturally I would like my notion of equality over this type to be equivalent to equality over the value component. This is all fine except that I run into problems when proving properties of lifted notions of this equality (for example equality over lists of this type).

For example:

open import Data.Nat
open import Data.Product
open import Relation.Binary
open import Relation.Binary.PropositionalEquality
open import Relation.Binary.List.Pointwise renaming (Rel to ListRel) hiding (refl)

module Test where

    _≈_ : Rel (ℕ × ℕ) _
    (a₁ , _) ≈ (a₂ , _) = a₁ ≡ a₂

    ≈-sym : Symmetric _≈_
    ≈-sym refl = refl

    ≋-sym : Symmetric (ListRel _≈_)
    ≋-sym = symmetric ≈-sym

In the last line Agda complains that it can't solve for the second projections out of the pairs. Interestingly changing the last line to the following eta-equivalent expression means that it can solve them:

    ≋-sym = symmetric (λ {x} {y} → ≈-sym {x} {y})

Now naturally I know that sometimes Agda can't solve for all the implicit arguments and needs a bit of extra help but I don't understand what new information I'm providing it by doing this...

I'm doing a lot of lifting of equality and I'd rather avoid adding these ugly eta expansions everywhere in my code. I was wondering if anyone has any suggestions to allow something similar to the original code to pass?

I have looked into irrelevance but the second projection is used elsewhere, even if it is computationally irrelevant for equality.


Solution

  • I'm guessing, but I think the problem is that the order of implicit arguments doesn't matter for (a part of) unification. Consider

    flipped : (({n m : ℕ} -> n ≡ m) -> ℕ) -> ({m n : ℕ} -> n ≡ m) -> ℕ
    flipped k f = k f
    

    Here k receives something of type {n m : ℕ} -> n ≡ m and f is of type {m n : ℕ} -> n ≡ m (m comes before n), but Agda happily unifies these two expressions, since each implicit argument becomes a metavariable during elaboration and metas are not ordered by when they was introduced — they are ordered by how they was instantiated (e.g. you can't instantiate α to β -> β and then β to α as it would result in α ≡ α -> α and handling such loops (called equirecursive types) is an unsound nightmare). So when you write

     ≋-sym = symmetric ≈-sym
    

    Agda is confused, because it could mean any of

     ≋-sym = symmetric (λ {x} {y} → ≈-sym {x} {y})
     ≋-sym = symmetric (λ {x} {y} → ≈-sym {y} {x})
    

    (well, not quite, because the second expression is ill-typed, but Agda doesn't backtrack and can't solve such problems therefore)

    This is different from the flipped example, because flipped explicitly specifies how n and m are used, so unification of

    {n1 m1 : ℕ} -> n1 ≡ m1
    {m2 n2 : ℕ} -> n2 ≡ m2
    

    results in n1 ≡ n2 and m1 ≡ m2 and hence the problem is solved. If you drop this specification

    unsolved : (({n m : ℕ} -> ℕ) -> ℕ) -> ({m n : ℕ} -> ℕ) -> ℕ
    unsolved k f = k f
    

    you'll get unsolved metas back.

    The exact problem with your definition is that only the first projections of pairs are mentioned in the RHS of _≈_, so Agda doesn't know how to unify the second projections. Here is a workaround:

    record Sing {α} {A : Set α} (x : A) : Set where
    
    module Test where
    
        _≈_ : Rel (ℕ × ℕ) _
        (a₁ , b₁) ≈ (a₂ , b₂) = a₁ ≡ a₂ × Sing (b₁ , b₂)
    
        ≈-sym : Symmetric _≈_
        ≈-sym (refl , _) = (refl , _)
    
        ≋-sym : Symmetric (ListRel _≈_)
        ≋-sym = symmetric ≈-sym
    

    Sing is a dummy record that have only one inhabitant that can be inferred automatically. But Sing allows to mention b₁ and b₂ in the RHS of _≈_ in an inference-friendly way, which makes those metas in ≋-sym solvable.

    Though, it seems that (a₁ , b₁) ≈ (a₂ , b₂) = a₁ ≡ a₂ × Sing b₁ already gives Agda enough hints how to solve metas in ≋-sym.

    You can also define a pattern synonym to make the code slightly nicer:

    pattern refl₁ = (refl , _)
    
    ≈-sym : Symmetric _≈_
    ≈-sym refl₁ = refl₁