Skip to content

Commit 6b5dcc1

Browse files
first half how-to contradiction
1 parent e5c847e commit 6b5dcc1

File tree

1 file changed

+302
-0
lines changed

1 file changed

+302
-0
lines changed

src/How_to_Ltac2_contradiction.v

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
(** * How to write a tactic Contracition with Ltac2
2+
3+
*** Authors
4+
- Thomas Lamiaux
5+
6+
*** Summary
7+
8+
This tutorial explains how to write a contradiction tactic using Ltac2.
9+
In particular, it showcase how to use quoting, and match goal with
10+
the Constr API to check properties on terms.
11+
12+
*** Table of content
13+
14+
- 1.
15+
16+
*** Prerequisites
17+
18+
Disclaimer:
19+
20+
Needed:
21+
- Basic knowledge of Ltac2
22+
23+
Installation:
24+
- Ltac2 and its core-library are available by default with Rocq
25+
26+
*)
27+
28+
From Ltac2 Require Import Ltac2 Constr Printf.
29+
30+
(**
31+
According to its specification the [contradiction] can take an argument or not.
32+
If [contradiction] does not take an argument, then [contradiction]:
33+
34+
1. First introduces all variables,
35+
2. Try to prove the goal by checking the hypothesis for:
36+
- An hypothesis [i : I] such that [I] is an inductive type without any constructor
37+
like [False], i.e. such that any goal can be proven by [destruct i]
38+
- An hypothesis [ni : ~I] such that [I] is an inductive type with
39+
one constructor without hypotheses, like [True] or [0 = 0].
40+
In other word, such [I] is provable by [constructor 1].
41+
- A pair of hypotheses [p : P] and [np :~P], such that any goal can be
42+
proven by [exact (False_rect _ (np p))]
43+
44+
If [contradiction] takes an argument [t] then, the type of [t] must either be:
45+
1. An empty like [False], in which case the goal is proven
46+
2. Or a negation [~P], in which case:
47+
- There is an hypotheis [P] in the context, then the goal is proven
48+
- Othewise, the goal is replace by [P]
49+
*)
50+
51+
52+
(** ** 1. Step 1:
53+
54+
*** 1.1 A simplified version
55+
56+
Let us start by writing a simplified first version without checking if
57+
the types are empty or are singleton types.
58+
This already require matching over goals and quoting.
59+
60+
*)
61+
62+
(* Ltac2 err_contradiction : exn :=
63+
Tactic_failure (Some (fprintf "There are no hypothesis solvable by contradiction")). *)
64+
65+
(** To look up for a pair of hypotheses [P] and [~P], we match the goal using
66+
[lazy_match! goal with].
67+
The patterns are of the form [ [name_hyp1 : type, ..., name_hyp1 : type,] ]
68+
69+
The name of all the variables and meta-variables must start for a small cap.
70+
To match for [P] and [~P], we hence use the pattern [p : ?t, np : ~(?t)].
71+
This provides us the ? of the hypotheses [p : ident] and [q : ident],
72+
and with the type [t : constr].
73+
74+
[[
75+
lazy_match! goal with
76+
| [ p : ?t, q : ~(?t) |- _ ] => ???
77+
| [ |- _ ] => Control.zero (Tactic_failure (Some (fprintf "There are no hypothesis solvable by contradiction")))
78+
end.
79+
]]
80+
81+
To prove the goal, we want to apply [destruct [np p]].
82+
This is not directly possible as [np] and [p] are idents, and not terms.
83+
Consequently, what we want is to apply the term that [np] refers to
84+
the the term that [p] refers to.
85+
86+
To get the term associated to the ident, we first use [Control.hyp : ident -> constr]
87+
to get a Ltac2 ??? out of an ident. We then use [$] to go back to a Rocq term.
88+
This provide us with the script:
89+
90+
[[
91+
let p := Control.hyp p in
92+
let q := Control.hyp q in
93+
exact (False_rect _ ($q $p))
94+
]]
95+
96+
Putting it all together, this give us the following Ltac2 term:
97+
*)
98+
99+
Goal forall (P : Prop), P -> ~P -> 0 = 1.
100+
intros.
101+
lazy_match! goal with
102+
| [ p : ?t, np : ~(?t) |- _ ] =>
103+
let p := Control.hyp p in
104+
let np := Control.hyp np in
105+
exact (False_rect _ ($np $p))
106+
| [ |- _ ] => Control.zero (Tactic_failure (Some (fprintf "There are no hypothesis solvable by contradiction")))
107+
end.
108+
109+
(** There are two subtilities to understand here about.
110+
111+
1. Matching is syntactic. This means that [P -> False] will not be matched
112+
by the pattern [~ P], even though [~P] is convertible to [P -> False].
113+
*)
114+
115+
Goal forall (P : Prop), P -> (P -> False) -> 0 = 1.
116+
intros.
117+
Fail lazy_match! goal with
118+
| [ p : ?t, np : ~(?t) |- _ ] =>
119+
let p := Control.hyp p in
120+
let np := Control.hyp np in
121+
exact (False_rect _ ($np $p))
122+
| [ |- _ ] => Control.zero (Tactic_failure (Some (fprintf "There are no hypothesis solvable by contradiction")))
123+
end.
124+
125+
(** 2. However, matching for meta-variables is up to conversion.
126+
This can be seen by adding a trivial expension around [P]:
127+
*)
128+
129+
Goal forall (P : Prop), P -> (~((fun _ => P) 0)) -> 0 = 1.
130+
intros.
131+
lazy_match! goal with
132+
| [ p : ?t, np : ~(?t) |- _ ] =>
133+
let p := Control.hyp p in
134+
let np := Control.hyp np in
135+
exact (False_rect _ ($np $p))
136+
| [ |- _ ] => Control.zero (Tactic_failure (Some (fprintf "There are no hypothesis solvable by contradiction")))
137+
end.
138+
139+
(** The two other cases proceed similarly. We use:
140+
- [match! goal] to find the hypothesis,
141+
- [Control.hyp] and [$] to use the rocq term associated to the hypotheses.
142+
143+
In the first case, we use [ltac2:(tac)] to build a term using the tactic [constructor].
144+
Note, that here, we really need to use [match] as in the absence of a check to ensure
145+
[t] can be proven by [constructor 1], it may fail.
146+
In which, case we want to backtrack to try the next hypothesis of the form [~t].
147+
*)
148+
149+
Goal ~(0 = 0) -> 0 = 1.
150+
intros.
151+
match! goal with
152+
| [ np : ~(_) |- _] =>
153+
let np := Control.hyp np in
154+
exact (False_rect _ ($np ltac2:(constructor 1)))
155+
| [ |- _ ] => Control.zero (Tactic_failure (Some (fprintf "There are no hypothesis solvable by contradiction")))
156+
end.
157+
Qed.
158+
159+
(** In the second case, in the absence of a test to check if [t] is an empty type,
160+
[destruct $p] could succeed without solving the goal, for instance for [t := nat].
161+
We hence, link [destruct $p] with [fail] to ensure the goal is solved,
162+
as if they are any remaining goal after [destruct $p], [fail] wil [fail].
163+
*)
164+
165+
Goal False -> 0 = 1.
166+
intros.
167+
match! goal with
168+
| [ p : _ |- _] =>
169+
let p := Control.hyp p in
170+
destruct $p; fail
171+
| [ |- _ ] => Control.zero (Tactic_failure (Some (fprintf "There are no hypothesis solvable by contradiction")))
172+
end.
173+
174+
(** Putting this altogether, this gives us the following tactic *)
175+
176+
Ltac2 contradiction_empty_v1_ : unit -> unit :=
177+
fun () =>
178+
match! goal with
179+
| [ p : ?t, np : ~(?t) |- _ ] =>
180+
let p := Control.hyp p in
181+
let np := Control.hyp np in
182+
exact (False_rect _ ($np $p))
183+
| [ np : ~(_) |- _] =>
184+
let np := Control.hyp np in
185+
exact (False_rect _ ($np ltac:(constructor)))
186+
| [ p : _ |- _ ] =>
187+
let p := Control.hyp p in
188+
destruct $p; fail
189+
| [ |- _] => let err_message := fprintf "No hypothesis is an obvious contradiction" in
190+
Control.zero (Tactic_failure (Some err_message))
191+
end.
192+
193+
(** We can now define an abreviation for it and try it out *)
194+
195+
Ltac2 Notation contradiction_empty_v1 := intros; contradiction_empty_v1_ ().
196+
197+
Goal (1 = 0) -> ~(1 = 0) -> 0 = 1.
198+
contradiction_empty_v1.
199+
Qed.
200+
201+
Goal ~(0 = 0) -> 0 = 1.
202+
contradiction_empty_v1.
203+
Qed.
204+
205+
Goal False -> 0 = 1.
206+
contradiction_empty_v1.
207+
Qed.
208+
209+
210+
(** ** 2. Using Constr.Unsafe to add syntactic check *)
211+
212+
213+
(*
214+
- need for the test
215+
- intro to unsafe
216+
- empty
217+
- singleton
218+
219+
220+
*)
221+
222+
223+
Ltac2 decompose_app (c : constr) :=
224+
match Unsafe.kind c with
225+
| Unsafe.App f cl => (f, cl)
226+
| _ => (c,[| |])
227+
end.
228+
229+
(* singletong types *)
230+
Ltac2 singleton_type (t : constr) : bool :=
231+
let (i, _) := decompose_app t in
232+
match Unsafe.kind i with
233+
| (Unsafe.Ind x _) => Int.equal (Ind.nblocks (Ind.data x)) 1
234+
| _ => false
235+
end.
236+
237+
(* empty types *)
238+
Ltac2 empty_types (t : constr) : bool :=
239+
let (i, _) := decompose_app t in
240+
match Unsafe.kind i with
241+
| (Unsafe.Ind x _) => Int.equal (Ind.nconstructors (Ind.data x)) 0
242+
| _ => false
243+
end.
244+
245+
Ltac2 contradiction_empty_v2_ : unit -> unit := fun () =>
246+
match! goal with
247+
| [ p : ?t, np : ?t -> False |- _ ] =>
248+
let p := Control.hyp p in
249+
let np := Control.hyp np in
250+
exact (False_rect _ ($np $p))
251+
| [ np : ~(?t) |- _] =>
252+
if singleton_type t
253+
then let np := Control.hyp np in
254+
exact (False_rect _ ($np ltac:(constructor)))
255+
else fail
256+
| [ p : ?t |- _ ] =>
257+
if empty_types t
258+
then let p := Control.hyp p in
259+
destruct $p
260+
else fail
261+
| [ |- _] => let err_message := fprintf "No hypothesis is an obvious contradiction" in
262+
Control.zero (Tactic_failure (Some err_message))
263+
end.
264+
265+
Ltac2 Notation contradiction_empty_v2 := contradiction_empty_v2_ ().
266+
267+
(** 3. [contradition_arg] *)
268+
269+
270+
271+
(** 4. Combining [contradiction_empty] and [contradition_arg], and Notation *)
272+
273+
(* Ltac2 my_contradiction2_ : unit -> unit := fun () =>
274+
(* get an hypothesis *)
275+
match! goal with
276+
| [ p : ?t1 |- _ ] =>
277+
let p := Control.hyp p in
278+
(* 1. If [~t] is an empty type *)
279+
if empty_types t1 then destruct $p else
280+
(* 2. If [~t] is the negation of a singleton type *)
281+
match! t1 with
282+
| ?t -> False =>
283+
if singleton_type t
284+
then exact (False_rect _ ($p ltac:(constructor 1)))
285+
else fail
286+
(* 3. Search for [~t] *)
287+
| _ =>
288+
match! goal with
289+
(* t is rebind here! *)
290+
| [q : ?t2 -> False |- _] =>
291+
if Constr.equal t1 t2 then
292+
printf "type q: %t" t2;
293+
let q := Control.hyp q in
294+
exact (False_rect _ ($q $p))
295+
else fail
296+
| [|- _] =>
297+
let err_message := fprintf "No hypothesis is an obvious contradiction" in
298+
Control.zero (Tactic_failure (Some err_message))
299+
end
300+
end
301+
end. *)
302+

0 commit comments

Comments
 (0)