The first thing that you will want to do when using REWRITE_TAC
or its family tactic is
to find out which rewrite rules to apply.
If you think someone has already proven the rewrite rule as a lemma because it is a well
known property, you can search it using the search
command.
The search
command takes a list of patterns and prints the theorems containing
all of them.
# search [`x + y + z`; `(x + y) + z`];;
val it : (string * thm) list =
[("ADD_AC",
|- m + n = n + m /\ (m + n) + p = m + n + p /\ m + n + p = n + m + p);
("ADD_ASSOC", |- !m n p. m + n + p = (m + n) + p)]
You can also give name "..."
as an argument to search
to choose theorems that
have the string in their names. The help
document has more info.
If you think the rewrite rule that you want to use is too specific to the case,
you can build the rewrite rule on-the-fly using various *_RULE
functions.
For example, if you want to apply a rewrite rule x * (y + z + w) = x * y + x * z + x * w
on
some natural number expression,
you can use ARITH_RULE
as follows:
e(REWRITE_TAC[ARITH_RULE `x * (y + z + w) = x * y + x * z + x * w`]);;
Useful rules are:
ARITH_RULE
: for natural numbersINT_ARITH
: for integersREAL_ARITH
: for real numbersTAUT
: for tautology (see TAUT)MESON[]
,METIS[]
: for first-order logic (see MESON, METIS)WORD_RULE
,WORD_ARITH
,WORD_BITWISE_RULE
,WORD_BLAST
,BITBLALST_RULE
: for bit-vectors (N word
type)
Assuming that some equality e = e'
is the rewrite rule that you want to create on-the-fly,
if e'
is supposed to be a typical result of some known simplicification of e
, you can use conversions.
For example, NUM_REDUCE_CONV `1 + 2`
returns a new rewrite rule `1 + 2 = 3`
, and you can use it
to rewrite any 1 + 2
in the conclusion with 3
.
# NUM_REDUCE_CONV `1 + 2`;;
val it : thm = |- 1 + 2 = 3
Note that ARITH_RULE `1 + 2 = 3`
will create the same rewrite rule too.
However, unlike NUM_REDUCE_CONV
, you need to explicitly give the result of addition at the right-hand side,
which can be cumbersome.
NUM_REDUCE_CONV
: takes a termt
which is a numerical expression and createst = t'
wheret'
is the calculated result oft
.REWRITE_CONV[]
: a conversion to create a rewrite rule using other rewrite rules on the fly! (see REWRITE_CONV)
If you could find rewrite rules that look relevant to what you wanted, you can try to derive your wanted rule using them.
GSYM
: If you foundmy_thm
sayinge1 = e2
, but you wantede2 = e1
,GSYM my_thm
will returne2 = e1
.
# ADD_ASSOC;;
val it : thm = |- !m n p. m + n + p = (m + n) + p
# GSYM ADD_ASSOC;;
val it : thm = |- !m n p. (m + n) + p = m + n + p
TRANS
: If you foundthm1
which ise1 = e2
andthm2
which ise2 = e3
,TRANS thm1 thm2
will returne1 = e3
.
# TRANS (ARITH_RULE `1 + 1 = 2`) (ARITH_RULE `2 = 0 + 2`);;
val it : thm = |- 1 + 1 = 0 + 2
MATCH_MP
: Ifthm1
isP ==> e1 = e2
andthm2
isP
,MATCH_MP thm1 thm2
will returne1 = e2
.MATCH_MP
can specialize universally quantified variables.
# MOD_LT;;
val it : thm = |- !m n. m < n ==> m MOD n = m
# MATCH_MP MOD_LT (ARITH_RULE `1 < 2`);;
val it : thm = |- 1 MOD 2 = 1
REWRITE_RULE
:REWRITE_RULE[rewrite_rules] rule0
returns a new rewrite rule that is a result of rewritingrewrite_rules
atrule0
(see REWRITE_RULE).
If the rewrite rule you want to apply already exists as an assumption, you can use it using a proper tactic.
If you want to apply all available rewrite rules in assumption, ASM_REWRITE_TAC[]
is the right tactic.
If you want to pick the assumption "H0" and use that, USE_THEN "H0"
can be used.
(if you want to know how to name assumptions, please refer to PlayingWithAssumptions.md)
# e(USE_THEN "H0" (fun th -> REWRITE_TAC[th]));;
Alternatively, if there is an assumption x + y = z
and you want to pick that, you can use
REWRITE_TAC[ASSUME `x + y + z`]
.
PlayingWithAssumptions.md has more info about how to utilize assumptions.
REWRITE_TAC[<rewrite_rules>]
will recursively traverse the conclusion from top to down (root to leafs) and rewrite
the subexpression whenever it matches one of the rewrite rules.
If you want to apply one of the rewrite rules only once, ONCE_REWRITE_TAC[<rewrite_rules>]
will work.
The order of rules in <rewrite_rules>
should not matter (see ONCE_REWRITE_TAC).
If you want to apply, e.g., ADD_SYM
which is |- !m n. m + n = n + m
only when m
is 1,
the rewrite rule can be specialized using SPEC
as follows.
# SPEC `1` ADD_SYM;;
val it : thm = |- !n. 1 + n = n + 1
This can be used as REWRITE_TAC[SPEC `1` ADD_SYM]
, or like this using the let
binding in OCaml:
e(let add_sym_1 = SPEC `1` ADD_SYM in
REWRITE_TAC[add_sym_1]);;
If you want to specialize multiple quantifiers, you can use SPECL [<list of exprs>] <theorem>
.
To explicitly designate the locations to rewrite, GEN_REWRITE_TAC <location> [<rewrite_rules>]
can be used (see: GEN_REWRITE_TAC).
Unlike REWRITE_TAC
, this does not recursively traverse the expression by default.
Instead, it precisely rewrites at the given <location>
(which is a function from conv
to conv
)
with respect to the root node.
Making it recursively traverse can be done by composing <location>
with
a family of DEPTH_CONV
, which will be explained later.
A few interesting <location>
are:
LAND_CONV
(LAND_CONV) applies the rewrites to the left-hand argument of binary operator. Note that, in the following example,y + x
in the left-hand side expression isn't rewritten tox + y
becauseGEN_REWRITE_TAC
does not recursively traverse into the subexpression.
# ADD_SYM;;
val it : thm = |- !m n. m + n = n + m
# g `(y + x) + z = z + (x + y)`;;
Warning: Free variables in goal: x, y, z
val it : goalstack = 1 subgoal (1 total)
`(y + x) + z = z + x + y`
# e(GEN_REWRITE_TAC LAND_CONV [ADD_SYM]);;
val it : goalstack = 1 subgoal (1 total)
`z + y + x = z + x + y`
- Similarly,
RAND_CONV
(RAND_CONV) applies to the right-hand side of binary operator. Actually, this applies to any operand of an application becausebinop l r = (binop l) r
. RATOR_CONV
(RATOR_CONV) applies to the operatorf
of an applicationf x
. If the expression isf x y
, this applies tof x
becausef x y
is(f x) y
.PAT_CONV <pattern>
(PAT_CONV) specifies the subexpression to rewrite using<pattern>
which is a lambda function. The parameter of lambda function works as a 'placeholder' to the pattern to match.
# g `(x - x) + (x - x) = (x - x)`;;
Warning: Free variables in goal: x
val it : goalstack = 1 subgoal (1 total)
`x - x + x - x = x - x`
# e(GEN_REWRITE_TAC (PAT_CONV `\x. x + a = b`) [ARITH_RULE `x - x = 0`]);;
val it : goalstack = 1 subgoal (1 total)
`0 + x - x = x - x`
PATH_CONV <path>
(PATH_CONV) specifies the subexpression to rewrite using a string<path>
.PATH_CONV
starts from the root node and follows which child to take according to<path>
.<path>
is a string of one of three charactersl
(for operator),r
(for operand) andb
(for the body of an abstraction).- And many more
_CONV
functions!
These locations can be composed together using the o
operator.
For example, LAND_CONV o RAND_CONV
points to the right-hand argument of the left-hand argument of
the binary operator.
To make GEN_REWRITE_TAC
recursively traverse the expression, the above *_CONV
functions
can be composed with a family of DEPTH_CONV
functions.
# g `!x (f:num->num). f ((x - x) + (x - x)) = f(x - x)`;;
val it : goalstack = 1 subgoal (1 total)
`!x f. f (x - x + x - x) = f (x - x)`
# e(REPEAT GEN_TAC);;
val it : goalstack = 1 subgoal (1 total)
`f (x - x + x - x) = f (x - x)`
# e(GEN_REWRITE_TAC (PAT_CONV `\x. x + a`) [ARITH_RULE `x - x = 0`]);;
(* This fails because GEN_REWRITE_TAC does not recursively traverse the conclusion by default. *)
Exception: Failure "REWRITES_CONV".
# e(GEN_REWRITE_TAC (DEPTH_CONV o (PAT_CONV `\x. x + a`)) [ARITH_RULE `x - x = 0`]);;
val it : goalstack = 1 subgoal (1 total)
`f (0 + x - x) = f (x - x)`
DEPTH_CONV
traverses in bottom-up order (DEPTH_CONV).
It can be slow because it repeats traversal until there is no applicable rewrite.
To reduce its cost, cheaper functions (ONCE_DEPTH_CONV
, TOP_DEPTH_CONV
, TOP_SWEEP_CONV
, ...)
can be considered.
DEPTH_BINOP_CONV
conditionally traverses the expression only if its operator is exactly a given symbol:
(* Given a goal that is `w1:int64 = w2 /\ w3:int64 = w4`, convert this to
`val w1 = val w2 /\ val w3 = val w4` using `GSYM VAL_EQ`.
This works even if the goal has more than one conjunction. *)
GEN_REWRITE_TAC (DEPTH_BINOP_CONV `(/\):bool->bool->bool`) [GSYM VAL_EQ]
Given a rewrite rule P ==> e1 = e2
,
you might want to replace e1
with e2
using that.
Its simple solutions can be categorized into two classes.
- Use tactics that always rewrite
e1
withe2
, leaving the necessary preconditionP
unproven, and prove it later to show that the rewrite(s) was okay. - Prove
P
first, makinge1 = e2
by modus ponens, and then safely rewritee1
withe2
.
If there are multiple subexpressions that match e1
, and replacing all of them
with e2
is safe,
the first solution is simpler in general because it provides specialized expressions of P
for each rewrite occurrence.
If the second solution was to be used in this case, you will have to manually specialize
P
for each matching of e1
, which will be laborsome.
However, the first solution may inadvertantly rewrite e1
with e2
even if the precondition
may not hold in that case.
In this case, the first solution must be avoided because this will make the goal not provable.
IMP_REWRITE_TAC[<the_rule>]
rewrites the expression and
leaves the condition P
at the conclusion as a conjunction.
# MOD_ADD_CASES;;
val it : thm =
|- !m n p.
m < p /\ n < p
==> (m + n) MOD p = (if m + n < p then m + n else (m + n) - p)
# g `(x + y) MOD 10 = 0`;;
Warning: Free variables in goal: x, y
val it : goalstack = 1 subgoal (1 total)
`(x + y) MOD 10 = 0`
# e(IMP_REWRITE_TAC[MOD_ADD_CASES]);;
val it : goalstack = 1 subgoal (1 total)
`(if x + y < 10 then x + y else (x + y) - 10) = 0 /\ x < 10 /\ y < 10`
If the conclusion was implication Q ==> R
and the rewritten part was in Q
,
P
is added as an assumption of Q
like this.
# MOD_LT;;
val it : thm = |- !m n. m < n ==> m MOD n = m
# g `(1 + 2) MOD 5 = 3 ==> 1 + 3 = 4`;;
val it : goalstack = 1 subgoal (1 total)
`(1 + 2) MOD 5 = 3 ==> 1 + 3 = 4`
# e(IMP_REWRITE_TAC[MOD_LT]);;
val it : goalstack = 1 subgoal (1 total)
`(1 + 2 < 5 ==> 1 + 2 = 3) ==> 1 + 3 = 4`
1 + 2 < 5
can be focused using ANTS_TAC
and then be proven.
This will make the conclusion 1 + 2 = 3 ==> 1 + 3 = 4
.
Here are variants of IMP_REWRITE_TAC
:
- If the order of rules to apply is important,
SEQ_IMP_REWRITE_TAC
is the tactic to use (see SEQ_IMP_REWRITE_TAC). - If you want to do a case analysis on its precondition
P
, you can useCASE_REWRITE_TAC
. From a goalQ
, it generates(P ==> Q') /\ (~P ==> Q)
whereQ'
is derived fromQ
via the rewrites. See CASE_REWRITE_TAC.
When is this approach good?
As described before, a benefit of this solution is that it automatically generates
specialized preconditions.
For example, MOD_LT
's precondition m < n
in the second example was automatically specialized
to 1 + 2 < 5
according to its matched expression.
Also, if there are more than one match, it generates multiple specialized preconditions:
# g `(x + y) MOD 10 = 0 /\ (x + y*2) MOD 20 = 0`;;
Warning: Free variables in goal: x, y
val it : goalstack = 1 subgoal (1 total)
`(x + y) MOD 10 = 0 /\ (x + y * 2) MOD 20 = 0`
# e(IMP_REWRITE_TAC[MOD_LT]);;
val it : goalstack = 1 subgoal (1 total)
`(x + y = 0 /\ 0 < 10) /\ x + y * 2 = 0 /\ 0 < 20`
SIMP_TAC[<rules>]
tactic will try to prove the preconditions of conditional
rewrite rules and then rewrite the goal using them.
If assumptions in the goal can be used to prove the preconditions, or there
is a conditional rewrite rule in assumptions, you can use ASM_SIMP_TAC
(see SIMP_TAC and ASM_SIMP_TAC).
An example from SIMP_CONV will give some insight:
# SUB_ADD;;
val it : thm = |- !m n. n <= m ==> m - n + n = m
# SIMP_CONV[SUB_ADD; ARITH_RULE `0 < n ==> 1 <= n`]
`0 < n ==> (n - 1) + 1 = n + f(k + 1)`;;
val it : thm =
|- 0 < n ==> n - 1 + 1 = n + f (k + 1) <=> 0 < n ==> n = n + f (k + 1)
If this automatic approach does not work, the precondition must be manually proven and be applied to the conditional rewrite rule. You can use either
-
MATCH_MP <conditional rule> <proof to the precondition>
: it creates a rewrite rule with its precondition relaxed. You might want to refer to this section as well. -
If the precondition needs a large proof that does not fit in few lines,
MP_TAC <conditional rule>
+ANTS_TAC
+ (proof of the precondition) +DISCH_THEN(fun th -> REWRITE_TAC[th])
will work. If the conditional rule has universally quantified variables,MP_TAC (SPECL [..(exprs)..] <conditional rule>)
will work.
TODO: TARGET_REWRITE_TAC