I have a function that uses rewrite
to satisfy the Agda type checker. I thought that I had a reasonably good grasp of how to deal with the resulting "vertical bars" in proofs about such functions. And yet, I fail completely at dealing with these bars in my seemingly simple case.
Here are the imports and my function, step
. The rewrite
s make Agda see that n
is equal to n + 0
and that suc (acc + n)
is equal to acc + suc n
, respectively.
module Repro where
open import Relation.Binary.PropositionalEquality as P using (_≡_)
open import Data.Nat
open import Data.Nat.DivMod
open import Data.Nat.DivMod.Core
open import Data.Nat.Properties
open import Agda.Builtin.Nat using () renaming (mod-helper to modₕ)
step : (acc d n : ℕ) → modₕ acc (acc + n) d n ≤ acc + n
step zero d n rewrite P.sym (+-identityʳ n) = a[modₕ]n<n n (suc d) 0
step (suc acc) d n rewrite P.sym (+-suc acc n) = a[modₕ]n<n acc (suc d) (suc n)
Now for the proof, which pattern matches on acc
, just like the function. Here's the zero
case:
step-ok : ∀ (acc d n : ℕ) → step acc d n ≡ a[modₕ]n<n acc d n
step-ok zero d n with n | P.sym (+-identityʳ n)
step-ok zero d n | .(n + 0) | P.refl = ?
At this point, Agda tells me I'm not sure if there should be a case for the constructor P.refl, because I get stuck when trying to solve the following unification problems (inferred index ≟ expected index): w ≟ w + 0 [...]
I am also stuck in the second case, the suc acc
case, albeit in a different way:
step-ok (suc acc) d n with suc (acc + n) | P.sym (+-suc acc n)
step-ok (suc acc) d n | .(acc + suc n) | P.refl = ?
Here, Agda says suc (acc + n) != w of type ℕ when checking that the type [...] of the generated with function is well-formed
Update after Sassa NF's response
I followed Sassa NF's advice and reformulated my function with P.subst
instead of rewrite
. I.e., I changed my right-hand side from being about n + 0
to being about n
, instead of conversely changing the goal from being about n
to being about n + 0
:
step′ : (acc d n : ℕ) → modₕ acc (acc + n) d n ≤ acc + n
step′ zero d n = P.subst (λ # → modₕ 0 # d # ≤ #) (+-identityʳ n) (a[modₕ]n<n n (suc d) 0)
step′ (suc acc) d n = P.subst (λ # → modₕ (suc acc) # d n ≤ #) (+-suc acc n) (a[modₕ]n<n acc (suc d) (suc n))
During the proof, the P.subst
in the function definition needs to be eliminated, which can be done with a with
construct:
step-ok′ : ∀ (acc d n : ℕ) → step′ acc d n ≡ a[modₕ]n<n acc d n
step-ok′ zero d n with n + 0 | +-identityʳ n
... | .n | P.refl = P.refl
step-ok′ (suc acc) d n with acc + suc n | +-suc acc n
... | .(suc (acc + n)) | P.refl = P.refl
So, yay! I just finished my very first Agda proof involving a with
.
Some progress on the original problem
My guess would be that my first issue is a unification issue during dependent pattern matching: there isn't any substitution that makes n
identical to n + 0
. More generally, in situations where one thing is a strict subterm of the other thing, I suppose that we may run into unification trouble. So, maybe using with
to match n
with n + 0
was asking for problems.
My second issue seems to be what the Agda language reference calls an ill-typed with
-abstraction. According to the reference, this "happens when you abstract over a term that appears in the type of a subterm of the goal or argument types." The culprit seems to be the type of the goal's subterm a[modₕ]n<n (suc acc) d n
, which is modₕ [...] ≤ (suc acc) + n
, which contains the subterm I abstract over, (suc acc) + n
.
It looks like this is usually resolved by additionally abstracting over the part of the goal that has the offending type. And, indeed, the following makes the error message go away:
step-ok (suc acc) d n with suc (acc + n) | P.sym (+-suc acc n) | a[modₕ]n<n (suc acc) d n
... | .(acc + suc n) | P.refl | rhs = {!!}
So far so good. Let's now introduce P.inspect
to capture the rhs
substitution:
step-ok (suc acc) d n with suc (acc + n) | P.sym (+-suc acc n) | a[modₕ]n<n (suc acc) d n | P.inspect (a[modₕ]n<n (suc acc) d) n
... | .(acc + suc n) | P.refl | rhs | P.[ rhs-eq ] = {!!}
Unfortunately, this leads to something like the original error: w != suc (acc + n) of type ℕ when checking that the type [...] of the generated with function is well-formed
One day later
Of course I'd run into the same ill-typed with-abstraction again! After all, the whole point of P.inspect
is to preserve a[modₕ]n<n (suc acc) d n
, so that it can construct the term a[modₕ]n<n (suc acc) d n ≡ rhs
. However, preserved a[modₕ]n<n (suc acc) d n
of course still has its preserved original type, modₕ [...] ≤ (suc acc) + n
, whereas rhs
has the modified type modₕ [...] ≤ acc + suc n
. That's what's causing trouble now.
I guess one solution would be to use P.subst
to change the type of the term we inspect. And, indeed, the following works, even though it is hilariously convoluted:
step-ok (suc acc) d n with suc (acc + n) | P.sym (+-suc acc n) | a[modₕ]n<n (suc acc) d n | P.inspect (λ n → P.subst (λ # → modₕ (suc acc) # d n ≤ #) (P.sym (+-suc acc n)) (a[modₕ]n<n (suc acc) d n)) n
... | .(acc + suc n) | P.refl | rhs | P.[ rhs-eq ] rewrite +-suc acc n = rhs-eq
So, yay again! I managed to fix my original second issue - basically by using P.subst
in the proof instead of in the function definition. It seems, though, that using P.subst
in the function definition as per Sassa NF's guidance is preferable, as it leads to much more concise code.
The unification issue is still a little mysterious to me, but on the positive side, I unexpectedly learned about the benefits of irrelevance on top of everything.
I'm accepting Sassa NF's response, as it put me on the right track towards a solution.
<=
should be a proposition, meaning for alln, m
andp q : n <= m
,p = q
.step-ok
then follows directly. – Jannis Limperg<=
intoProp
, though, does it? It seems to be inSet
. So, Agda wouldn't know that<=
is a proposition, no? Again, thanks for sharing this. I am intrigued to look into this more. – 123omnomnomP
such that∀ (p q : P), p ≡ q
. IfP : Prop
, this is true definitionally. But for someP : Set
, you can still prove this as a lemma. The standard library already has it:Data.Nat.Properties.≤-irrelevant
. – Jannis Limperg