Library coqrel.Delay
This file implements a tactical delaying tac and a tactic
delay, which function in the following way: tac is able to
invoke delay to solve any subgoal. Assuming the subgoal can be
typed in the context in which delaying tac was invoked, the
delay tactic will succeed, and the subgoal will instead become a
subgoal of the encolosing invocation of delaying tac.
To see why this is useful, consider the way eauto works. Applying
an eauto hint is only considered successful when the generated
subgoal can themselves be solved by eauto. Otherwise, eauto
backtracks and another hint is used. If no hint can be used to solve
the goal completely, then no progress is made.
While this behavior is usually appropriate, this means eauto
cannot be used as a base for tactics which make as much progress as
possible, then let the user (or another tactic in a sequence) solve
the remaining subgoals. With the tactics defined below, this can be
accomplished by registering an external hint making use of delay
for strategic subgoals, and invoking delaying eauto.
Additionally, the variant delaying_conjunction bundles all of the
subgoals solved by delay in a single conjunction of the form
P1 ∧ ... ∧ Pn ∧ True. This is useful as it provides a uniform
interface when reifying the effect of a tactic.
The delayed/delay tactics
The delaying tactical and the delay tactic communicate
through the use of existential variables. Specifically, delaying
introduces a so-called "open conjunction" to the context. The open
conjunction starts as a hypothesis of type ?P, and remains of
the form P1 ∧ ... ∧ Pn ∧ ?Q throughout. Each invocation of
delay unifies ?Q with a new node Pi ∧ ?Q'. Pi will be of
the form ∀ x1 .. xn, delayed_goal G, where G is the
original goal and the xis are the new variables which were
introduced after the open conjunction was created.
Ltac use_conjunction H :=
lazymatch type of H with
| ?A ∧ ?B ⇒
use_conjunction (@proj2 A B H)
| _ ⇒
eapply (proj1 H)
end.
As much as possible, our open conjunction should remain hidden
from the user's point of view. To avoid interfering with random
tactics such as eassumption, we wrap it in the following record
type.
The evar can only be instantiated to refer to variables that
existed at the point where the evar is spawned. As a consequence,
in order to use it, we must first get rid of all of the new
variables, which the goal potentially depends on. We do this by
reverting the context one variable at a time from the bottom up,
until we hit our open_conjunction.
Ltac revert_until_conjunction Hdelayed :=
match goal with
| |- open_conjunction _ → _ ⇒
intro Hdelayed
| H : _ |- _ ⇒
revert H;
revert_until_conjunction Hdelayed
end.
To make sure this operation is reversible, we first wrap the
goal using the following marker.
In some contexts, we actually want a nested saved goal to be
split up further instead. The following marker does that.
Once we're done, we've packaged all of the goals we solved using
use_conjunction into a single hypothesis. We can use the
following tactic to split up that conjunction into individual
subgoals again.
Although we expect the conjunctions produced by use_conjunction
to be terminated by True and this should be regarded as the
canonical format, when the last conjunct is a non-True
proposition we consider it as an additional subgoal. Likewise we
don't require the conclusions to be wrapped in delayed_goal.
This allows for some flexibility when dealing with manually
written goals.
Ltac split_conjunction :=
let handle_subgoal :=
intros;
match goal with
| |- delayed_goal _ ⇒ red
| |- unpack _ ⇒ red; split_conjunction
| |- _ ⇒ idtac
end in
match goal with
| |- _ ∧ _ ⇒ split; [handle_subgoal | split_conjunction]
| |- _ ⇒ exact I
| |- _ ⇒ handle_subgoal
end.
Ltac delay :=
idtac;
lazymatch goal with
| _ : open_conjunction _ |- ?G ⇒
change (delayed_goal G);
let Hdelayed := fresh "Hdelayed" in
revert_until_conjunction Hdelayed;
use_conjunction (open_conjunction_proj _ Hdelayed)
| _ ⇒
fail "delay can only be used under the 'delayed' tactical"
end.
Ltac delayed_conjunction tac :=
let Pv := fresh in evar (Pv: Prop);
let P := eval red in Pv in clear Pv;
let Hdelayed := fresh "Hdelayed" in
cut (open_conjunction P);
[ intro Hdelayed; tac; clear Hdelayed |
eapply Build_open_conjunction ].
Ltac delayed tac :=
delayed_conjunction tac;
[ .. | split_conjunction ].
Ltac undelay :=
lazymatch goal with
| Hdelayed : open_conjunction _ |- _ ⇒
clear Hdelayed
end.
Ltac nondelayed tac :=
undelay; tac.
End Delay.
Tactic Notation "delayed" tactic1(tac) :=
Delay.delayed tac.
Tactic Notation "nondelayed" tactic1(tac) :=
Delay.nondelayed tac.
Tactic Notation "delayed_conjunction" tactic1(tac) :=
Delay.delayed_conjunction tac.
Tactic Notation "delay" := Delay.delay.
Hints
This typeclass wrapper is convenient for performing a nested
search outside of a potential "delayed" context.
Class NonDelayed (P: Prop) :=
nondelayed : P.
Hint Extern 1 (NonDelayed _) ⇒
red; try Delay.undelay : typeclass_instances.