[Agda] Case split on λ()

Marcus Christian Lehmann lehmann at tet.tu-berlin.de
Fri Mar 15 02:27:28 CET 2019


Hello Martin,

Thank you for the guidance and once more for the comprehensive example! 
I'll try to turn them into progress:

To convince Agda, that ¬( 2 ≡ 2 ) is empty "is" to get
> thm : ¬( 2 ≡ 2 ) → 𝟘
> thm p = p (refl 2)
type checked.

Applying this to the concrete problem, my line of thought is the following:

For two functions f and g of type B → B, the type of all choices that 
"make f and g differ" I think of as

Σ[ x ∈ B ] ¬(P x)

where
     P : A → Set
     P x = f x ≡ g x

and when explicitly constructing such pair

x1 : Σ[ x ∈ B ] ¬(P x)
x1 = …some-construction… , (λ ())

it is impossible to give a construction where P x holds, because then 
it'd be impossible to give (λ ()) accepted for ¬(P x).

But now, when this becomes an assumption, so we receive an hypothetical 
inhabitant of this type "Σ[ x ∈ B ] ¬(P x)" as in

thm : Σ[ x ∈ B ] ¬(P x) → ⊤
thm (x , ¬p) = ?

then (when we are in the hole `?`) there "are" hypothetical inhabitants  
x : B and ¬p : ¬(P x) available.

We might split x into it's constructors over and over. And cases may 
occur where in their holes `?` it is possible to construct an inhabitant 
refl : P x out of the given, hypothetical inhabitants. Then these cases 
should not matter at all: it is impossible to give an actual inhabitant 
of "Σ[ x ∈ B ] ¬(P x) " that would make such construction possible. I am 
sorry about wrong terminology, when referring to this as an 
"uninhabitated case"; and grateful for every correction.

 From your example I get a more detailed understanding of where 
Data.Empty.⊥-elim is rooted (in 𝟘-recursion) and when filling such 
uninhabitated holes, I understand ⊥-elim (¬p refl) as giving "nothing, 
but of the right type".

Now if we assume that I may just not see refl : P x being constructable 
in such case's hole `?`, then I would not notice that this is an 
uninhabitated case. I understand that my not-noticing is perfectly fine, 
and the construction I might come up without ⊥-elim will never occur on 
an actual inhabitant of Σ[ x ∈ B ] ¬(P x).

So I think "having" a mystical inhabitant  of 𝟘 / ⊥ as "currently 
deriving in an unreachable case" (every time 𝟘 occurs as a hypothesis).

The goal `… → ⊤` in my signature makes `thm` trivial. Even in an 
uninhabitated case one can give tt : ⊤ without the necessity of using 
𝟘-recursion / ⊥-elim. But with a non-trivial goal, one might has no 
other choice to obtain the right type for the goal.

Similarly I would think that every time ⊥-elim / 𝟘-recursion is used, 
the case is also uninhabitated anyways.

I guess the more proper question is: how to enumerate (or just 
"observe") all inhabitated cases of a theorem (function)? (And is that 
even a sound thing to ask?)

I know that the compactness of that, i.e the amount of cases, depends on 
how clever x is split with regard to f and g.  And I might want to split 
often enough to eliminate f and g, for which they better not be 
recursive. So it might be an approach from a wrong direction, but to 
obtain this information, I tried to come up with a proper function 
signature and "look" at the cases to figure out whether they are 
inhabitated.

My current understanding is that for a case-split Agda expands all the 
constructors and removes every case that is rendered impossible by 
unification (e.g. when an inhabitant of ≡ was deduced).

So I think, when one is really up for "eliminating" a case, in a way 
that we will not end up "implementing" it, then a signature needs to be 
set up that puts an impossible constraint in the assumptions of a 
function, but just in that case. It was puzzling me a day how to achieve 
this for ≢ and whether it can be done at all.

Again, thank you for providing the insights: they aid a lot for 
reviewing literature and experimenting with Agda.

regards,

Am 14.03.19 um 23:39 schrieb Martin Escardo:
>
> Let me give a complete example of this situation written in Agda,
> without solving your specific problem (deliberately), in a
> self-contained way:
>
> ----
> data _≡_ {X : Set} : X → X → Set where
>   refl : (x : X) → x ≡ x
>
> data 𝟘 : Set where
>
> 𝟘-induction : (A : 𝟘 → Set) → (x : 𝟘) → A x
> 𝟘-induction A ()
>
> -- The above should be the only place you would ever *need* to use the
> -- () pattern, as Martin-Löf type theory teaches us.
>
> -- Even better, if the data definition of the empty type 𝟘 has zero
> -- constructors, then 𝟘-induction should be defined by zero
> -- equations in an ideal world.
>
> 𝟘-recursion : (B : Set) → 𝟘 → B
> 𝟘-recursion B = 𝟘-induction (λ (_ : 𝟘) → B)
>
> ¬ : Set → Set
> ¬ X = X → 𝟘
>
> -- Example of how the pattern () can be avoided once we have
> -- 𝟘-induction:
>
> data Colour : Set where
>  Black White : Colour
>
> not-black-is-white : (c : Colour) → ¬ (c ≡ Black) → c ≡ White
> not-black-is-white Black f = 𝟘-recursion (Black ≡ White) (f (refl 
> Black))
> not-black-is-white White f = refl White
>
> -- Here f : c ≡ Black → 𝟘.
>
> -- We don't pattern match on f, because it is a function.  We use it.
> -- To use it, we have to apply it to an argument.  Once we have the
> -- argument available, we get a mythical element of 𝟘, with which we
> -- can do anything we like, using 𝟘-recursion.
> ----
> Martin
>
> On 14/03/2019 20:33, Martin Escardo wrote:
>>
>>
>> On 14/03/2019 16:36, lehmann at tet.tu-berlin.de wrote:
>>> convincing Agda that ¬ 2 ≡ 2 should be empty
>>
>> I am not going to give a solution in Agda. Instead, I will try to guide
>> you.
>>
>>   * The type `2 ≡ 2` has an inhabitant. (By definition of `≡`.)
>>
>>   * To say that a type `X` is empty amounts to saying that we have an
>>     inhabitant of `¬ X`.
>>
>>     That is, that you have a function X → R where R is the empty type.
>>
>>   * So you are trying to show that `¬(¬ 2 ≡ 2)` has an inhabitant.
>>
>>   * But for any type `A`, if `A` has an inhabitant, then so does `¬¬A`.
>>
>>     If you are a logician, you know that.
>>
>>     If you are a functional programmer, what you are trying to do is
>>     from an inhabitant of the type `A` to get an inhabitant of the type
>>     `(A → R) → R`, where `R` is the empty type.
>>
>>     But this can actually be done for an arbitrary type `R`, not just
>>     the empty type. It is the evaluation map.
>>
>>   * Now if you read "`¬ (2 ≡ 2)` is empty" as `(2 ≡ 2 → R) → R` for `R`
>>     the empty type, you are done.
>>
>> I hope this helps. Pattern matching is not what is needed here.
>>
>> Martin
>>
>>
>>
>>>
>>> ```
>>> fun : ¬ (2 ≡ 2) → ⊥
>>> fun () -- Error: "¬ 2 ≡ 2 should be empty, but that's not obvious to 
>>> me"
>>> ```
>>>
>>>
>>> Am 14.03.19 um 09:09 schrieb Apostolis Xekoukoulotakis:
>>>> Marcus, I could not follow your example, but it seems that you are
>>>> trying to case split a function, which is not possible.
>>>>
>>>> ```
>>>> open import Relation.Binary.PropositionalEquality
>>>> open import Data.Nat
>>>> open import Data.Empty
>>>> open import Relation.Nullary
>>>>
>>>>
>>>> fun : ¬ (2 ≡ 2) → ⊥
>>>> fun ¬x =  ? -- ⊥-elim (¬x refl)
>>>>
>>>> ```
>>>
>>>
>>> _______________________________________________
>>> Agda mailing list
>>> Agda at lists.chalmers.se
>>> https://lists.chalmers.se/mailman/listinfo/agda
>>>
>>
>> -- 
>> Martin Escardo
>> http://www.cs.bham.ac.uk/~mhe
>> _______________________________________________
>> Agda mailing list
>> Agda at lists.chalmers.se
>> https://lists.chalmers.se/mailman/listinfo/agda
>
> -- 
> Martin Escardo
> http://www.cs.bham.ac.uk/~mhe


-- 
Marcus Christian Lehmann, M.Sc.
Research Associate

Technische Universität Berlin
Institute of High-Frequency and Semiconductor System Technologies
Faculty IV – Electrical Engineering and Computer Science
Department Theoretische Elektrotechnik

Secretariat EN 2
Einsteinufer 17
D-10587 Berlin
Germany
  
Phone: +49 (30) 314-21363
Mail:lehmann at tet.tu-berlin.de



More information about the Agda mailing list