move_add: a tactic for moving summands #
Calling move_add [a, ← b, c], recursively looks inside the goal for expressions involving a sum.
Whenever it finds one, it moves the summands that unify to a, b, c, removing all parentheses.
See the doc-string for tactic.interactive.move_add for more information.
Implementation notes #
This file defines a general move_op tactic, intended for reordering terms in an expression
obtained by repeated applications of a given associative, commutative binary operation. The
user decides the final reordering. Applying move_op without specifying the order will simply
remove all parentheses from the expression.
The main user-facing tactics are move_add and move_mul, dealing with addition and
multiplication, respectively.
In what is below, we talk about move_add for definiteness, but everything applies
to move_mul and to the more general move_op.
The implementation of move_add only moves the terms specified by the user (and rearranges
parentheses).
Note that the tactic abel already implements a very solid heuristic for normalizing terms in an
additive commutative semigroup and produces expressions in more or less standard form.
The scope of move_add is different: it is designed to make it easy to move individual terms
around a sum.
Future work #
- Add support for
neg/div/invin additive/multiplicative groups? - Currently the tactic has special support for
+and*. Every other operation is outsourced toac_refl(see the proof ofreorder_hyp). Should there be the desire for specialized support of other operations (e.g.∪, ∩, ⊓, ⊔, ...), that is the definition to modify, at least in the first instance. - Add functionality for moving terms across the two sides of an in/dis/equality.
E.g. it might be desirable to have
to_lhs [a]convertingb + c = a + dto- a + b + c = d. - Add a non-recursive version for use in
convmode. - Revise tests?
Throughout this file, op : pexpr denotes an arbitrary (binary) operation. We do not use,
but implicitly imagine, that this operation is associative, since we extract iterations of
such operations, with complete disregard of the order in which these iterations arise.
Given a list un of αs and a list bo of bools, return the sublist of un
consisting of the entries of un whose corresponding entry in bo is tt.
Used for error management: un is the list of user inputs, bo is the list encoding which input
is unused (tt) and which input is used (ff).
return_unused returns the unused user inputs.
If bo is shorter than un, return_unused will include the remainder of un.
Equations
- tactic.move_op.return_unused (u :: us) (b :: bs) = ite ↥b (u :: tactic.move_op.return_unused us bs) (tactic.move_op.return_unused us bs)
- tactic.move_op.return_unused (hd :: tl) list.nil = hd :: tl
- tactic.move_op.return_unused list.nil (hd :: tl) = list.nil
- tactic.move_op.return_unused list.nil list.nil = list.nil
Calling move_add [a, ← b, c], recursively looks inside the goal for expressions involving a sum.
Whenever it finds one, it moves the summands that unify to a, b, c, removing all parentheses.
Repetitions are allowed, and are processed following the user-specified ordering.
The terms preceded by a ← get placed to the left, the ones without the arrow get placed to the
right. Unnamed terms stay in place. Due to re-parenthesizing, doing move_add with no argument
may change the goal. Also, the order in which the terms are provided matters: the tactic reads
them from left to right. This is especially important if there are multiple matches for the typed
terms in the given expressions.
A single call of move_add moves terms across different sums in the same expression.
Here is an example.
import tactic.move_add
example {a b c d : ℕ} (h : c = d) : c + b + a = b + a + d :=
begin
move_add [← a, b], -- Goal: `a + c + b = a + d + b` -- both sides changed
congr,
exact h
end
example {a b c d : ℕ} (h : c = d) : c + b * c + a * c = a * d + d + b * d :=
begin
move_add [_ * c, ← _ * c], -- Goal: `a * c + c + b * c = a * d + d + b * d`
-- the first `_ * c` unifies with `b * c` and moves to the right
-- the second `_ * c` unifies with `a * c` and moves to the left
congr;
assumption
end
The list of expressions that move_add takes is optional and a single expression can be passed
without brackets. Thus move_add ← f and move_add [← f] mean the same.
Finally, move_add can also target one or more hypotheses. If hp₁, hp₂ are in the
local context, then move_add [f, ← g] at hp₁ hp₂ performs the rearranging at hp₁ and hp₂.
As usual, passing ⊢ refers to acting on the goal.
Reporting sub-optimal usage #
The tactic could fail to prove the reordering. One potential cause is when there are multiple matches for the rearrangements and an earlier rewrite makes a subsequent one fail. Another possibility is that the rearranged expression changes the Type of some expression and the tactic gets stumped. Please, report bugs and failures in the Zulip chat!
There are three kinds of unwanted use for move_add that result in errors, where the tactic fails
and flags the unwanted use.
move_add [vars]? at *reports globally unused variables and whether all goals are unchanged, not each unchanged goal.- If a target of
move_add [vars]? at targetsis left unchanged by the tactic, then this will be flagged (unless we are usingat *). - If a user-provided expression never unifies, then the variable is flagged.
In these cases, the tactic produces an error, reporting unused inputs and unchanged targets as appropriate.
For instance, move_add ← _ always fails reporting an unchanged goal, but never an unused variable.
Comparison with existing tactics #
-
tactic.interactive.abelperforms a "reduction to normal form" that allows it to close goals involving sums with higher success rate thanmove_add. If the goal is an equality of two sums that are simply obtained by reparenthesizing and permuting summands, thenmove_add [appropriate terms]can close the goal. Compared toabel,move_addhas the advantage of allowing the user to specify the beginning and the end of the final sum, so that from there the user can continue with the proof. -
tactic.interactive.ac_changesupports a wide variety of operations. At the moment,move_addworks with addition,move_mulworks with multiplication. There is the possibility of supporting other operations, using the non-interactive tactictactic.move_op. Still, on several experiments,move_addhad a much quicker performance thanac_change. Also, formove_addthe user need only specify a few terms: the tactic itself takes care of producing the full rearrangement and proving it "behind the scenes".
Remark: #
It is still possible that the same output of move_add [exprs] can be achieved by a proper sublist
of [exprs], even if the tactic does not flag anything. For instance, giving the full re-ordering
of the expressions in the target that we want to achieve will not complain that there are unused
variables, since all the user-provided variables have been matched. Of course, specifying the order
of all-but-the-last variable suffices to determine the permutation. E.g., with a goal of
a + b = 0, applying either one of move_add [b,a], or move_add a, or move_add ← b has the
same effect and changes the goal to b + a = 0. These are all valid uses of move_add.
See the doc-string for tactic.interactive.move_add and mentally
replace addition with multiplication throughout. ;-)