Concrete solution
You need to case-split on the same element on which you case-split in your function:
prop : ∀ x y → fun x y ≡ nothing → x < y
prop x y _ with x <? y
... | yes p = p
In the older versions of Agda, you would have had to write the following:
prop-old : ∀ x y → fun x y ≡ nothing → x < y
prop-old x y _ with x <? y
prop-old _ _ refl | yes p = p
prop-old _ _ () | no _
But now you are able to completely omit a case when it leads to a direct contradiction, which is, in this case, that nothing
and just smth
can never be equal.
Detailed explanation
To understand how with
works you first need to understand how definitional equality is used in Agda to reduce goals. Definitional equality binds a function call with its associated expression depending on the structure of its input. In Agda, this is easily seen by the use of the equal sign in the definition of the different cases of a function (although since Agda builds a tree of cases some definitional equalities might not hold in some cases, but let's forget this for now).
Let us consider the following definition of the addition over naturals:
_+_ : ℕ → ℕ → ℕ
zero + b = b
(suc a) + b = suc (a + b)
This definition provides two definitional equalities that bind zero + b
with b
and (suc a) + b
with suc (a + b)
. The good thing with definitional equalities (as opposed to propositional equalities) is that Agda automatically uses them to reduce goals whenever possible. This means that, for instance, if in a further goal you have the element zero + p
for any p
then Agda will automatically reduce it to p
.
To allow Agda to do such reduction, which is fundamental in most cases, Agda needs to know which of these two equalities can be exploited, which means a case-split on the first argument of this addition has to be made in any further proof about addition for a reduction to be possible. (Except for composite proofs based on other proofs which use such case-splits).
When using with
you basically add additional definitional equalities depending on the structure of the additional element. This only makes sense, understanding that, that you need to case-split on said element when doing proofs about such a function, in order for Agda once again to be able to make use of these definitional equalities.
Let us take your example and apply this reasoning to it, first without the recent ability to omit impossible cases. You need to prove the following statement:
prop-old : ∀ x y → fun x y ≡ nothing → x < y
Introducing parameters in the context, you write the following line:
prop-old x y p = ?
Having written that line, you need to provide a proof of x < y
with the elements in the context. x
and y
are just natural so you expect p
to hold enough information for this result to be provable. But, in this case, p
is just of type fun x y ≡ nothing
which does not give you enough information. However, this type contains a call to function fun
so there is hope ! Looking at the definition of fun
, we can see that it yields two definitional equalities, which depend on the structure of x <? y
. This means that adding this parameter to the proof by using with
once more will allow Agda to make use of these equalities. This leads to the following code:
prop-old : ∀ x y → fun x y ≡ nothing → x < y
prop-old x y p with x <? y
prop-old _ _ p | yes q = ?
prop-old _ _ p | no q = ?
At that point, not only did Agda case-split on x <? y
, but it also reduced the goal because it is able, in both cases, to use a specific definitional equality of fun
. Let us take a closer look at both cases:
In the yes q
case, p
is now of type nothing ≡ nothing
and q
is of type x < y
which is exactly what you want to prove, which means the goal is simply solved by:
prop-old _ _ p | yes q = q
I the no q
case, something more interesting happens, which is somewhat harder to understand. After reduction, p
is now of type just y ≡ nothing
because Agda could use the second definitional equality of fun
. Since _≡_
is a data type, it is possible to case-split on p
which basically asks Agda: "Look at this data type and give me all the possible constructors for an element of type just y ≡ nothing
". At first, Agda only finds one possible constructor, refl
, but this constructor only builds an element of a type where both sides of the equality are the same, which is not the case here by definition because just
and nothing
are two distinct constructors from the same data type, Maybe
. Agda then concludes that there are no possible constructors that could ever build an element of such type, hence this case is actually not possible, which leads to Agda replacing p
with the empty pattern ()
and dismissing this case. This line is thus simply:
prop-old _ _ () | no _
In the more recent versions of Agda, as I explained earlier, some of these steps are done directly by Agda which allows us to directly omit impossible cases when the emptiness of a pattern can be deduced behind the curtain, which leads to the prettier:
prop : ∀ x y → fun x y ≡ nothing → x < y
prop x y _ with x <? y
... | yes p = p
But it is the same process, just done a bit more automatically. Hopefully, these elements will be of some use in your journey towards understanding Agda.
with
actually work at a more fundamental level. Maybe I am confused by the "type checking" system which seems magical to me. I am not quite sure how to explain what I am not sure of because I am not sure I understand Agda. Thank you for your answer, it was really illuminating – helq