Matching goals with variables#
Our first version works great when the conclusion matches exactly the shape of a lemma, but it doesn’t generalize well in the presence of variables. For example, if we introduced variables before running ApplySearch.run (), we would not find anything, despite the lemma still applying:
Open Scope list_scope.forall (A : Type) (x y z : list A), x ++ y = x ++ z -> y = zforall (A : Type) (x y z : list A), x ++ y = x ++ z -> y = z(* Now our tactic fails: *)A: Type
x, y, z: list Ax ++ y = x ++ z -> y = z(* Stuck. :( *) Abort.A: Type
x, y, z: list Ax ++ y = x ++ z -> y = z
To fix this issue, we’ll replace our variables with existential variables (evars in short), which are converted to pattern variables by Patternops.pattern_of_constr.
Turning variables into evars#
We introduce a new function, create_pattern, that creates the pattern by replacing every variable with a fresh evar:
19(* … *)
20let run () =
21 match%rocq goal with
22 | _, {| ?conclusion |} ->
23 let* env = Tactic.env in
24 let* sigma = Tactic.sigma in
25 let pattern = create_pattern env sigma conclusion in
26 print_results (search env sigma pattern);
27 return ()
The create_pattern method looks as follows:
4let create_pattern env sigma t =
5 let new_evar_for var (sigma, mappings) =
6 let typ = EConstr.of_constr (Environ.named_type var env) in
7 let sigma, evar = Evarutil.new_evar env sigma typ
8 ~src:(None, Evar_kinds.MatchingVar (Evar_kinds.FirstOrderPatVar var))
9 ~naming:(IntroIdentifier var)
10 in (sigma, (var, evar) :: mappings)
11 in
12 let vars = Termops.collect_vars sigma t in
13 let sigma, mappings = Names.Id.Set.fold new_evar_for vars (sigma, []) in
14 Patternops.pattern_of_constr env sigma
15 (EConstr.Vars.replace_vars sigma mappings t)
16
17(* … *)
We use several low-level functions from Rocq APIs:
Evarutil.new_evarcreates a new evar in the evar map, returning the updated evar map and the fresh evar;Termops.collect_varsreturns the set of named variables in the term;EConstr.Vars.replace_varsreplaces every variable inmappingswith its second element.
Our file now looks as follows:
1Require Import Camltac.Camltac.
2
3Camltac Module ApplySearch := ocaml:{{
4 let create_pattern env sigma t =
5 let new_evar_for var (sigma, mappings) =
6 let typ = EConstr.of_constr (Environ.named_type var env) in
7 let sigma, evar = Evarutil.new_evar env sigma typ
8 ~src:(None, Evar_kinds.MatchingVar (Evar_kinds.FirstOrderPatVar var))
9 ~naming:(IntroIdentifier var)
10 in (sigma, (var, evar) :: mappings)
11 in
12 let vars = Termops.collect_vars sigma t in
13 let sigma, mappings = Names.Id.Set.fold new_evar_for vars (sigma, []) in
14 Patternops.pattern_of_constr env sigma
15 (EConstr.Vars.replace_vars sigma mappings t)
16
17 let search env sigma pattern =
18 let results = ref [] in
19 let collect_result res _ _ _ _ = results := res :: !results in
20 Search.search_pattern env sigma pattern (SearchOutside []) collect_result;
21 !results
22
23 let print_results = function
24 | [] -> Feedback.msg_info (Pp.str "No theorem could be applied.")
25 | results ->
26 let open Pp in
27 let pr_ref ref = Libnames.pr_path (Ltac2.Env.path ref) in
28 let pp = List.fold_left
29 (fun acc res -> acc ++ fnl () ++ pr_ref res)
30 (str "Try this: ") results in
31 Feedback.msg_info pp
32
33 let run () =
34 match%rocq goal with
35 | _, {| ?conclusion |} ->
36 let* env = Tactic.env in
37 let* sigma = Tactic.sigma in
38 let pattern = create_pattern env sigma conclusion in
39 print_results (search env sigma pattern);
40 return ()
41}}.
If we rerun the tactic, we now obtain the correct results:
From Stdlib Require List. Open Scope list_scope.forall (A : Type) (x y z : list A), x ++ y = x ++ z -> y = zforall (A : Type) (x y z : list A), x ++ y = x ++ z -> y = zA: Type
x, y, z: list Ax ++ y = x ++ z -> y = zA: Type
x, y, z: list Ax ++ y = x ++ z -> y = z