Pattern-matching tactics¶
Author: | Clément Pit-Claudel |
---|---|
Contact: | clement.pitclaudel@live.com |
Date: | 2017-10-13 |
module FStar.Tactics.PatternMatching // JP: this file does not seem to type-check without this option. #set-options "--use_two_phase_tc true --admit_smt_queries true"
Contents¶
Motivation¶
Suppose you have a goal of the form squash (a == b)
. How do you capture
a and b for further inspection?
Here’s a basic (but cumbersome!) implementation:
open FStar.Tactics let fetch_eq_side () : Tac (term * term) = let g = cur_goal () in match inspect g with | Tv_App squash (g, _) -> (match inspect squash with | Tv_FVar squash -> if fv_to_string squash = flatten_name squash_qn then (match inspect g with | Tv_App eq_type_x (y, _) -> (match inspect eq_type_x with | Tv_App eq_type (x, _) -> (match inspect eq_type with | Tv_App eq (typ, _) -> (match inspect eq with | Tv_FVar eq -> if fv_to_string eq = flatten_name eq2_qn then (x, y) else fail "not an equality" | _ -> fail "not an app2 of fvar: ") | _ -> fail "not an app3") | _ -> fail "not an app2") | _ -> fail "not an app under squash") else fail "not a squash" | _ -> fail "not an app of fvar at top level") | _ -> fail "not an app at top level"
…and here’s how you could use it:
let _ = assert_by_tactic (1 + 1 == 2) (fun () -> let l, r = fetch_eq_side () in print (term_to_string l ^ " / " ^ term_to_string r))
This file defines pattern-matching primitives that let you write the same thing like this…
…or, more succinctly, like this:
Some utility functions¶
(Skip over this part on a quick read — these are just convenience functions)
(** Ensure that tactic `t` fails. **) let mustfail #a (t: unit -> Tac a) (message: string) : Tac unit = match trytac t with | Some _ -> fail message | None -> ()
The following two tactics are needed because of issues with the Tac
effect.
let implies_intro' () : Tac unit = let _ = implies_intro () in () let repeat' #a (f: unit -> Tac a) : Tac unit = let _ = repeat f in () let and_elim' (h: binder) : Tac unit = and_elim (pack (Tv_Var (bv_of_binder h))); clear h (** Use a hypothesis at type a to satisfy a goal at type squash a *) let exact_hyp (a: Type0) (h: binder) : Tac unit = let hd = quote (FStar.Squash.return_squash #a) in exact (mk_app hd [((pack (Tv_Var (bv_of_binder h))), Q_Explicit)]) (** Use a hypothesis h (of type a) to satisfy a goal at type a *) let exact_hyp' (h: binder): Tac unit = exact (pack (Tv_Var (bv_of_binder h))) let print_binder (b: binder) : Tac unit = print (term_to_string (type_of_binder b))
Pattern types¶
Patterns are defined using a simple inductive type, mirroring the structure
of term_view
.
type varname = string type qn = string type pattern = | PAny: pattern | PVar: name: varname -> pattern | PQn: qn: qn -> pattern | PType: pattern | PApp: hd: pattern -> arg: pattern -> pattern let desc_of_pattern = function | PAny -> "anything" | PVar _ -> "a variable" | PQn qn -> "a constant (" ^ qn ^ ")" | PType -> "Type" | PApp _ _ -> "a function application" let rec string_of_pattern = function | PAny -> "__" | PVar x -> "?" ^ x | PQn qn -> qn | PType -> "Type" | PApp l r -> "(" ^ string_of_pattern l ^ " " ^ string_of_pattern r ^ ")"
Pattern matching exceptions¶
Pattern-matching is defined as a pure, monadic function (because of issues with combining DM4F effects, but also because it helps with debugging). This section defines the exception monad.
Types of exceptions¶
noeq type match_exception = | NameMismatch of qn * qn | SimpleMismatch of pattern * term | NonLinearMismatch of varname * term * term | UnsupportedTermInPattern of term | IncorrectTypeInAbsPatBinder of typ let term_head t : Tac string = match inspect t with | Tv_Var bv -> "Tv_Var" | Tv_BVar fv -> "Tv_BVar" | Tv_FVar fv -> "Tv_FVar" | Tv_App f x -> "Tv_App" | Tv_Abs x t -> "Tv_Abs" | Tv_Arrow x t -> "Tv_Arrow" | Tv_Type () -> "Tv_Type" | Tv_Refine x t -> "Tv_Refine" | Tv_Const cst -> "Tv_Const" | Tv_Uvar i t -> "Tv_Uvar" | Tv_Let r b t1 t2 -> "Tv_Let" | Tv_Match t branches -> "Tv_Match" | Tv_AscribedT _ _ _ -> "Tv_AscribedT" | Tv_AscribedC _ _ _ -> "Tv_AscribedC" | Tv_Unknown -> "Tv_Unknown" let string_of_match_exception = function | NameMismatch (qn1, qn2) -> "Match failure (name mismatch): expecting " ^ qn1 ^ ", found " ^ qn2 | SimpleMismatch (pat, tm) -> "Match failure (sort mismatch): expecting " ^ desc_of_pattern pat ^ ", got " ^ term_to_string tm | NonLinearMismatch (nm, t1, t2) -> "Match failure (nonlinear mismatch): variable " ^ nm ^ " needs to match both " ^ (term_to_string t1) ^ " and " ^ (term_to_string t2) | UnsupportedTermInPattern tm -> "Match failure (unsupported term in pattern): " ^ term_to_string tm ^ " (" ^ term_head tm ^ ")" | IncorrectTypeInAbsPatBinder typ -> "Incorrect type in pattern-matching binder: " ^ term_to_string typ ^ " (use one of ``var``, ``hyp …``, or ``goal …``)"
The exception monad¶
noeq type match_res a = | Success of a | Failure of match_exception let return #a (x: a) : match_res a = Success x let bind (#a #b: Type) (f: match_res a) (g: a -> Tac (match_res b)) : Tac (match_res b) = match f with | Success aa -> g aa | Failure ex -> Failure ex let raise #a (ex: match_exception) : match_res a = Failure ex
Liftings¶
There’s a natural lifting from the exception monad into the tactic effect:
let lift_exn_tac #a #b (f: a -> match_res b) (aa: a) : Tac b = match f aa with | Success bb -> bb | Failure ex -> Tactics.fail (string_of_match_exception ex) let lift_exn_tactic #a #b (f: a -> match_res b) (aa: a) : Tac b = match f aa with | Success bb -> bb | Failure ex -> Tactics.fail (string_of_match_exception ex)
Pattern interpretation¶
This section implement pattern-matching. This is strictly a one term, one pattern implementation — handling cases in which mutliple hypotheses match the same pattern is done later.
type bindings = list (varname * term) let string_of_bindings (bindings: bindings) = String.concat "\n" (map (fun (nm, tm) -> (">> " ^ nm ^ ": " ^ term_to_string tm)) bindings) (** Match a pattern against a term. `cur_bindings` is a list of bindings collected while matching previous parts of the pattern. Returns a result in the exception monad. **) let rec interp_pattern_aux (pat: pattern) (cur_bindings: bindings) (tm:term) : Tac (match_res bindings) = admit(); let interp_any () cur_bindings tm = return [] in let interp_var (v: varname) cur_bindings tm = match List.Tot.assoc v cur_bindings with | Some tm' -> if term_eq tm tm' then return cur_bindings else raise (NonLinearMismatch (v, tm, tm')) | None -> return ((v, tm) :: cur_bindings) in let interp_qn (qn: qn) cur_bindings tm = match inspect tm with | Tv_FVar fv -> if fv_to_string fv = qn then return cur_bindings else raise (NameMismatch (qn, (fv_to_string fv))) | _ -> raise (SimpleMismatch (pat, tm)) in let interp_type cur_bindings tm = match inspect tm with | Tv_Type () -> return cur_bindings | _ -> raise (SimpleMismatch (pat, tm)) in let interp_app (p_hd p_arg: (p:pattern{p << pat})) cur_bindings tm = match inspect tm with | Tv_App hd (arg, _) -> with_hd <-- interp_pattern_aux p_hd cur_bindings hd; with_arg <-- interp_pattern_aux p_arg with_hd arg; return with_arg | _ -> raise (SimpleMismatch (pat, tm)) in match pat with | PAny -> interp_any () cur_bindings tm | PVar var -> interp_var var cur_bindings tm | PQn qn -> interp_qn qn cur_bindings tm | PType -> interp_type cur_bindings tm | PApp p_hd p_arg -> interp_app p_hd p_arg cur_bindings tm (** Match a pattern `pat` against a term. Returns a result in the exception monad. **) let interp_pattern (pat: pattern) : term -> Tac (match_res bindings) = fun (tm: term) -> rev_bindings <-- interp_pattern_aux pat [] tm; return (List.Tot.rev rev_bindings) (** Match a term `tm` against a pattern `pat`. Raises an exception if the match fails. This is mostly useful for debugging: use ``mgw`` to capture matches. **) let match_term pat (tm : term) : Tac bindings = match interp_pattern pat (norm_term [] tm) with | Success bb -> bb | Failure ex -> Tactics.fail (string_of_match_exception ex)
Pattern-matching problems¶
Generalizing past single-term single-pattern problems, we obtain the following notions of pattern-matching problems and solutions:
let debug msg : Tac unit = () // print msg
Definitions¶
let absvar = binder type hypothesis = binder
A matching problem is composed of holes (mp_vars
), hypothesis patterns
(mp_hyps
), and a goal pattern (mp_goal
).
noeq type matching_problem = { mp_vars: list varname; mp_hyps: list (varname * pattern); mp_goal: option pattern } let string_of_matching_problem mp = let vars = String.concat ", " mp.mp_vars in let hyps = String.concat "\n " (List.Tot.map (fun (nm, pat) -> nm ^ ": " ^ (string_of_pattern pat)) mp.mp_hyps) in let goal = match mp.mp_goal with | None -> "_" | Some pat -> string_of_pattern pat in "\n{ vars: " ^ vars ^ "\n" ^ " hyps: " ^ hyps ^ "\n" ^ " goal: " ^ goal ^ " }"
A solution is composed of terms captured to mach the holes, and binders captured to match hypothesis patterns.
noeq type matching_solution = { ms_vars: list (varname * term); ms_hyps: list (varname * hypothesis) } let string_of_matching_solution ms = let vars = String.concat "\n " (map (fun (varname, tm) -> varname ^ ": " ^ (term_to_string tm)) ms.ms_vars) in let hyps = String.concat "\n " (map (fun (nm, binder) -> nm ^ ": " ^ (binder_to_string binder)) ms.ms_hyps) in "\n{ vars: " ^ vars ^ "\n" ^ " hyps: " ^ hyps ^ " }" (** Find a varname in an association list; fail if it can't be found. **) let assoc_varname_fail (#b: Type) (key: varname) (ls: list (varname * b)) : Tac b = match List.Tot.assoc key ls with | None -> fail ("Not found: " ^ key) | Some x -> x let ms_locate_hyp (a: Type) (solution: matching_solution) (name: varname) : Tac binder = assoc_varname_fail name solution.ms_hyps let ms_locate_var (a: Type) (solution: matching_solution) (name: varname) : Tac a = unquote #a (assoc_varname_fail name solution.ms_vars) let ms_locate_unit (a: Type) _solution _binder_name : Tac unit = ()
Resolution¶
Solving a matching problem is a two-steps process: find an initial assignment for holes based on the goal pattern, then find a set of hypotheses matching hypothesis patterns.
Note that the implementation takes a continuation of type
matching_solution -> Tac a
. This continuation is needed because we want
users to be able to provide extra criteria on matching solutions (most
commonly, this criterion is that a particular tactic should run
successfuly).
This makes it easy to implement a simple for of search through the context, where one can find a hypothesis matching a particular predicate by constructing a trivial matching problem and passing the predicate as the continuation.
(** Scan ``hypotheses`` for a match for ``pat`` that lets ``body`` succeed. ``name`` is used to refer to the hypothesis matched in the final solution. ``part_sol`` includes bindings gathered while matching previous solutions. **) let rec solve_mp_for_single_hyp #a (name: varname) (pat: pattern) (hypotheses: list hypothesis) (body: matching_solution -> Tac a) (part_sol: matching_solution) : Tac a = match hypotheses with | [] -> fail #a "No matching hypothesis" | h :: hs -> or_else // Must be in ``Tac`` here to run `body` (fun () -> match interp_pattern_aux pat part_sol.ms_vars (type_of_binder h) with | Failure ex -> fail ("Failed to match hyp: " ^ (string_of_match_exception ex)) | Success bindings -> let ms_hyps = (name, h) :: part_sol.ms_hyps in body ({ part_sol with ms_vars = bindings; ms_hyps = ms_hyps })) (fun () -> solve_mp_for_single_hyp name pat hs body part_sol) (** Scan ``hypotheses`` for matches for ``mp_hyps`` that lets ``body`` succeed. **) let rec solve_mp_for_hyps #a (mp_hyps: list (varname * pattern)) (hypotheses: list hypothesis) (body: matching_solution -> Tac a) (partial_solution: matching_solution) : Tac a = match mp_hyps with | [] -> body partial_solution | (name, pat) :: pats -> solve_mp_for_single_hyp name pat hypotheses (solve_mp_for_hyps pats hypotheses body) partial_solution (** Solve a matching problem. The solution returned is constructed to ensure that the continuation ``body`` succeeds: this implements the usual backtracking-match semantics. **) let solve_mp #a (problem: matching_problem) (hypotheses: binders) (goal: term) (body: matching_solution -> Tac a) : Tac a = let goal_ps = match problem.mp_goal with | None -> { ms_vars = []; ms_hyps = [] } | Some pat -> match interp_pattern pat goal with | Failure ex -> fail ("Failed to match goal: " ^ (string_of_match_exception ex)) | Success bindings -> { ms_vars = bindings; ms_hyps = [] } in solve_mp_for_hyps #a problem.mp_hyps hypotheses body goal_ps
A DSL for pattern-matching¶
Using pattern-matching problems as defined above is relatively cumbersome, so we now introduce a lightweight notation, in two steps: pattern notations, and matching-problem notations.
Pattern notations¶
The first part of our pattern-matching syntax is pattern notations: we provide a reflective function which constructs a pattern from a term: variables are holes, free variables are constants, and applications are application patterns.
// This is a hack to allow users to capture anything. assume val __ : #t:Type -> t let any_qn = %`__ (** Compile a term `tm` into a pattern. **) let rec pattern_of_term_ex tm : Tac (match_res pattern) = match inspect tm with | Tv_Var bv -> return (PVar (name_of_bv bv)) | Tv_FVar fv -> let qn = fv_to_string fv in return (if qn = any_qn then PAny else PQn qn) | Tv_Type () -> return PType | Tv_App f (x, _) -> let is_any = match inspect f with | Tv_FVar fv -> fv_to_string fv = any_qn | _ -> false in if is_any then return PAny else (fpat <-- pattern_of_term_ex f; xpat <-- pattern_of_term_ex x; return (PApp fpat xpat)) | _ -> raise (UnsupportedTermInPattern tm) (** β-reduce a term `tm`. This is useful to remove needles function applications introduced by F*, like ``(fun a b c -> a) 1 2 3``. **) let beta_reduce (tm: term) : Tac term = norm_term [] tm (** Compile a term `tm` into a pattern. **) let pattern_of_term tm : Tac pattern = match pattern_of_term_ex tm with | Success bb -> bb | Failure ex -> Tactics.fail (string_of_match_exception ex)
Problem notations¶
We then introduce a DSL for matching problems, best explained on the following example:
(fun (a b c: ①) (h1 h2 h3: hyp ②) (g: goal ③) → ④)
This notation is intended to express a pattern-matching problems with three
holes a
, b
, and c
of type ①, matching hypotheses h1
, h2
,
and h3
against pattern ② and the goal against the pattern ③. The body
of the notation (④) is then run with appropriate terms bound to a
,
b
, and c
, appropriate binders bound to h1
, h2
, and h3
,
and ()
bound to g
.
We call these patterns abspat
s (abstraction patterns), and we provide
facilities to parse them into matching problems, and to run their bodies
against a particular matching solution.
// We used to annotate variables with an explicit 'var' marker, but then that // var annotation leaked into the types of other hypotheses due to type // inference, requiring non-trivial normalization. // let var (a: Type) = a let hyp (a: Type) = binder let goal (a: Type) = unit let hyp_qn = %`hyp let goal_qn = %`goal noeq type abspat_binder_kind = | ABKVar of typ | ABKHyp | ABKGoal let string_of_abspat_binder_kind = function | ABKVar _ -> "varname" | ABKHyp -> "hyp" | ABKGoal -> "goal" noeq type abspat_argspec = { asa_name: absvar; asa_kind: abspat_binder_kind } // We must store this continuation, because recomputing it yields different // names when the binders are re-opened. type abspat_continuation = list abspat_argspec * term let classify_abspat_binder binder : Tac (abspat_binder_kind * term) = let varname = "v" in let hyp_pat = PApp (PQn hyp_qn) (PVar varname) in let goal_pat = PApp (PQn goal_qn) (PVar varname) in let typ = type_of_binder binder in match interp_pattern hyp_pat typ with | Success [(_, hyp_typ)] -> ABKHyp, hyp_typ | Success _ -> fail "classifiy_abspat_binder: impossible (1)" | Failure _ -> match interp_pattern goal_pat typ with | Success [(_, goal_typ)] -> ABKGoal, goal_typ | Success _ -> fail "classifiy_abspat_binder: impossible (2)" | Failure _ -> ABKVar typ, typ (** Split an abstraction `tm` into a list of binders and a body. **) let rec binders_and_body_of_abs tm : Tac (binders * term) = match inspect tm with | Tv_Abs binder tm -> let binders, body = binders_and_body_of_abs tm in binder :: binders, body | _ -> [], tm let cleanup_abspat (t: term) : Tac term = norm_term [] t (** Parse a notation into a matching problem and a continuation. Pattern-matching notations are of the form ``(fun binders… -> continuation)``, where ``binders`` are of one of the forms ``var …``, ``hyp …``, or ``goal …``. ``var`` binders are typed holes to be used in other binders; ``hyp`` binders indicate a pattern to be matched against hypotheses; and ``goal`` binders match the goal. A reduction phase is run to ensure that the pattern looks reasonable; it is needed because F* tends to infer arguments in β-expanded form. The continuation returned can't directly be applied to a pattern-matching solution; see ``interp_abspat_continuation`` below for that. **) let matching_problem_of_abs (tm: term) : Tac (matching_problem * abspat_continuation) = let binders, body = binders_and_body_of_abs (cleanup_abspat tm) in debug ("Got binders: " ^ (String.concat ", " (map (fun b -> name_of_binder b <: Tac string) binders))); let classified_binders = map (fun binder -> let bv_name = name_of_binder binder in debug ("Got binder: " ^ bv_name ^ "; type is " ^ term_to_string (type_of_binder binder)); let binder_kind, typ = classify_abspat_binder binder in (binder, bv_name, binder_kind, typ)) binders in let problem = fold_left (fun problem (binder, bv_name, binder_kind, typ) -> debug ("Compiling binder " ^ name_of_binder binder ^ ", classified as " ^ string_of_abspat_binder_kind binder_kind ^ ", with type " ^ term_to_string typ); match binder_kind with | ABKVar _ -> { problem with mp_vars = bv_name :: problem.mp_vars } | ABKHyp -> { problem with mp_hyps = (bv_name, (pattern_of_term typ)) :: problem.mp_hyps } | ABKGoal -> { problem with mp_goal = Some (pattern_of_term typ) }) ({ mp_vars = []; mp_hyps = []; mp_goal = None }) classified_binders in let continuation = let abspat_argspec_of_binder xx : Tac abspat_argspec = match xx with | (binder, xx, binder_kind, yy) -> { asa_name = binder; asa_kind = binder_kind } in (map abspat_argspec_of_binder classified_binders, tm) in let mp = { mp_vars = List.rev #varname problem.mp_vars; mp_hyps = List.rev #(varname * pattern) problem.mp_hyps; mp_goal = problem.mp_goal } in debug ("Got matching problem: " ^ (string_of_matching_problem mp)); mp, continuation
Continuations¶
Parsing an abspat yields a matching problem and a continuation of type
abspat_continuation
, which is essentially just a list of binders and a
term (the body of the abstraction pattern).
(** Get the (quoted) type expected by a specific kind of abspat binder. **) let arg_type_of_binder_kind binder_kind : Tac term = match binder_kind with | ABKVar typ -> typ | ABKHyp -> `binder | ABKGoal -> `unit (** Retrieve the function used to locate a value for a given abspat binder. **) let locate_fn_of_binder_kind binder_kind = match binder_kind with | ABKVar _ -> `ms_locate_var | ABKHyp -> `ms_locate_hyp | ABKGoal -> `ms_locate_unit (** Construct a term fetching the value of an abspat argument from a quoted matching solution ``solution_term``. **) let abspat_arg_of_abspat_argspec solution_term (argspec: abspat_argspec) : Tac term = let loc_fn = locate_fn_of_binder_kind argspec.asa_kind in let name_tm = pack (Tv_Const (C_String (name_of_binder argspec.asa_name))) in let locate_args = [(arg_type_of_binder_kind argspec.asa_kind, Q_Explicit); (solution_term, Q_Explicit); (name_tm, Q_Explicit)] in mk_app loc_fn locate_args (** Specialize a continuation of type ``abspat_continuation``. This constructs a fully applied version of `continuation`, but it requires a quoted solution to be passed in. **) let specialize_abspat_continuation' (continuation: abspat_continuation) (solution_term:term) : Tac term = let mk_arg argspec = (abspat_arg_of_abspat_argspec solution_term argspec, Q_Explicit) in let argspecs, body = continuation in mk_app body (map mk_arg argspecs) (** Specialize a continuation of type ``abspat_continuation``. This yields a quoted function taking a matching solution and running its body with appropriate bindings. **) let specialize_abspat_continuation (continuation: abspat_continuation) : Tac term = let solution_binder = fresh_binder (`matching_solution) in let solution_term = pack (Tv_Var (bv_of_binder solution_binder)) in let applied = specialize_abspat_continuation' continuation solution_term in let thunked = pack (Tv_Abs solution_binder applied) in debug ("Specialized into " ^ (term_to_string thunked)); let normalized = beta_reduce thunked in debug ("… which reduces to " ^ (term_to_string normalized)); thunked (** Interpret a continuation of type ``abspat_continuation``. This yields a function taking a matching solution and running the body of the continuation with appropriate bindings. **) let interp_abspat_continuation (a:Type0) (continuation: abspat_continuation) : Tac (matching_solution -> Tac a) = let applied = specialize_abspat_continuation continuation in unquote #(matching_solution -> Tac a) applied
Putting it all together¶
We now have all we need to use pattern-matching, short of a few convenience functions:
(** Construct a matching problem from an abspat. **) let interp_abspat #a (abspat: a) : Tac (matching_problem * abspat_continuation) = matching_problem_of_abs (quote abspat) (** Construct an solve a matching problem. This higher-order function isn't very usable on its own — it's mostly a convenience function to avoid duplicating the problem-parsing code. **) let match_abspat #b #a (abspat: a) (k: abspat_continuation -> Tac (matching_solution -> Tac b)) : Tac b = let goal = cur_goal () in let hypotheses = binders_of_env (cur_env ()) in let problem, continuation = interp_abspat abspat in admit(); //NS: imprecision in the encoding of the impure result function type solve_mp #matching_solution problem hypotheses goal (k continuation) (** Inspect the matching problem produced by parsing an abspat. **) let inspect_abspat_problem #a (abspat: a) : Tac matching_problem = fst (interp_abspat #a abspat) (** Inspect the matching solution produced by parsing and solving an abspat. **) let inspect_abspat_solution #a (abspat: a) : Tac matching_solution = match_abspat abspat (fun _ -> (fun solution -> solution) <: Tac _) let tpair #a #b (x : a) : Tac (b -> Tac (a * b)) = fun (y: b) -> (x, y)
Our first convenient entry point!
This takes an abspat, parses it, computes a solution, and runs the body of the abspat with appropriate bindings. It implements what others call ‘lazy’ pattern-matching, so called because the success of the body of the pattern isn’t taken into account when deciding whether a particular set of matched hypothesis should be retained. In other words, it picks the first matching set of hypotheses, and commits to it.
If you think that sounds like a greedy algorithm, it does. That’s why it’s called ‘gpm’ below: greedy pattern-matching.
(** Solve a greedy pattern-matching problem and run its continuation. This if for pattern-matching problems in the ``Tac`` effect. **) let gpm #b #a (abspat: a) () : Tac b = let continuation, solution = match_abspat abspat tpair in interp_abspat_continuation b continuation solution
And here’s the non-greedy version of the same. It’s informative to compare the implementations! This one will only find assignments that let the body run successfuly.
(** Solve a greedy pattern-matching problem and run its continuation. This if for pattern-matching problems in the ``Tac`` effect. **) let pm #b #a (abspat: a) : Tac b = match_abspat abspat (interp_abspat_continuation b)
Examples¶
We conclude with a small set of examples.
open FStar.Tactics
Simple examples¶
Here’s the example from the intro, which we can now run!
let fetch_eq_side' #a : Tac (term * term) = gpm (fun (left right: a) (g: goal (squash (left == right))) -> (quote left, quote right)) () // TODO: GM: The following definition breaks extraction with (* FStar.Tactics.Effect.fst(20,16-20,21): (Error 76) Ill-typed application: application is FStar.Tactics.PatternMatching.fetch_eq_side' (FStar.Tactics.Types.incr_depth (FStar.Tactics.Types.set_proofstate_range ps (FStar.Range.prims_to_fstar_range FStar.Tactics.PatternMatching.fst(811,26-811,45)))) remaining args are FStar.Tactics.Types.incr_depth (FStar.Tactics.Types.set_proofstate_range ps (FStar.Range.prims_to_fstar_range FStar.Tactics.PatternMatching.fst(811,26-811,45))) ml type of head is (FStar_Reflection_Types.term * FStar_Reflection_Types.term) *) (* let _ = *) (* assert_by_tactic (1 + 1 == 2) *) (* (fun () -> let l, r = fetch_eq_side' #int in *) (* print (term_to_string l ^ " / " ^ term_to_string r)) *) let _ = assert_by_tactic (1 + 1 == 2) (gpm (fun (left right: int) (g: goal (squash (left == right))) -> let l, r = quote left, quote right in print (term_to_string l ^ " / " ^ term_to_string r) <: Tac unit))
Commenting out the following example and comparing pm
and gpm
can be
instructive:
(* let test_bt (a: Type0) (b: Type0) (c: Type0) (d: Type0) = assert_by_tactic ((a ==> d) ==> (b ==> d) ==> (c ==> d) ==> a ==> d) (fun () -> repeat' implies_intro'; gpm (fun (a b: Type0) (h: hyp (a ==> b)) -> print (binder_to_string h); fail "fail here" <: Tac unit); qed ()) *)
A real-life example¶
The following tactics combines mutliple simple building blocks to solve a
goal. Each use of lpm
recognizes a specific pattern; and each tactic is
tried in succession, until one succeeds. The whole process is repeated as
long as at least one tactic succeeds.
let example #a #b #c: unit = assert_by_tactic (a /\ b ==> c == b ==> c) (fun () -> repeat' (fun () -> gpm #unit (fun (a: Type) (h: hyp (squash a)) -> clear h <: Tac unit) `or_else` (fun () -> gpm #unit (fun (a b: Type0) (g: goal (squash (a ==> b))) -> implies_intro' () <: Tac unit) `or_else` (fun () -> gpm #unit (fun (a b: Type0) (h: hyp (a /\ b)) -> and_elim' h <: Tac unit) `or_else` (fun () -> gpm #unit (fun (a b: Type0) (h: hyp (a == b)) (g: goal (squash a)) -> rewrite h <: Tac unit) `or_else` (fun () -> gpm #unit (fun (a: Type0) (h: hyp a) (g: goal (squash a)) -> exact_hyp a h <: Tac unit) ()))))); qed ())
Possible extensions¶
The following tasks would make for interesting extensions of this experiment:
- Handling multiple goal patterns (easy)
- Extending the matching language (match under binders?)
- Introducing specialized syntax
- Thinking about a sound way of supporting ‘match-anything’ patterns in abspat notations
- Using the normalizer to partially-evaluated pattern-matching tactics
- Migrating to a compile-time version of
quote
Notes¶
The following should work, but it crashes F*: GM: Not anymore! :) But I sent a goal to smt.
let example2 #a #b #c: unit = assert_by_tactic (a /\ b ==> c == b ==> c) (fun () -> first #unit [gpm (fun (a: Type) (h: hyp (squash a)) -> clear h <: Tac unit); gpm (fun (a b: Type0) (_: goal (squash (a ==> b))) -> implies_intro' () <: Tac unit); gpm (fun (a b: Type0) (h: hyp (a /\ b)) -> and_elim' h <: Tac unit); gpm (fun (a b: Type0) (h: hyp (a == b)) (_: goal (squash a)) -> rewrite h <: Tac unit); gpm (fun (a: Type0) (h: hyp a) (_: goal (squash a)) -> exact_hyp a h <: Tac unit); idtac]; smt (); qed ())