lh-l4v/docs/compacting-proofs.md

6.6 KiB

Compacting proofs

Not all proofs are equal. After finishing a proof (especially a long, tricky one), look over the proof and try to compact it. This includes

  • removing or refactoring redundant steps; and
  • replacing fragile methods by more robust methods.

For example, a proof like

apply (drule ac_corres_ccorres_underlying)
apply (clarsimp simp: ccorres_underlying_def corres_underlying_def rf_sr_def Ball_def liftE_def)
apply (erule allE, erule allE, erule_tac P="cstate_relation _ _" in impE, assumption)
apply clarsimp
apply (erule allE, erule impE, rule_tac P="arg_rel _" and Q="¬snd _" in conjI, assumption, assumption)
apply (erule allE, erule allE, erule_tac P="Γ⊢⇩h ⟨_, _⟩ ⇒ _" in impE, assumption)
apply (rename_tac s s' n ret)
apply (case_tac ret; (simp; fail)?)
apply (clarsimp simp: in_liftE[simplified liftE_def])
apply (erule allE, erule allE, erule_tac P="_ ∈ fst _" in impE, assumption)
apply (auto simp: unif_rrel_def)

could be better written as

by (fastforce simp: ccorres_underlying_def corres_underlying_def rf_sr_def Ball_def liftE_def
                       unif_rrel_def in_liftE[simplified liftE_def]
              split: xstate.splits dest!: ac_corres_ccorres_underlying)

Another example from Hoare is the proof

apply clarify
apply(rename_tac ps b qs)
apply clarsimp
apply(rename_tac ps')
apply(rule_tac x = ps' in exI)
apply simp
apply(rule_tac x = "b#qs" in exI)
apply simp

It was rewritten to

apply(fastforce intro:notin_List_update[THEN iffD2])

Rationale

When proving a non-trivial theorem, the first attempt usually explores various possibilities, e.g. using erule and drule on assumptions and using clarsimp every other line. Similarly, program verification proofs tend to use wp for single steps, proofs of identities have many subst steps, and so on. Compacting such proofs usually improves them:

  • Removing the noise of many small steps makes proofs easier to read.
  • Proofs are less likely to break due to trivial definitional changes.
  • Sometimes, compacted proofs run faster.

Of course, while compacted proofs break less often, they may be harder to fix once they do break. Thus proofs should not be over-compressed to the point where the overall proof strategy becomes incomprehensible.

Example: classical reasoning

Worked example of how to shrink an erule-heavy first proof. The source was proof/crefine/AutoCorresTest, but the proof there has changed in the meantime.

This was the original fragment:

apply (drule ac_corres_ccorres_underlying)
apply (clarsimp simp: ccorres_underlying_def corres_underlying_def rf_sr_def Ball_def liftE_def)
apply (erule allE, erule allE, erule_tac P="cstate_relation _ _" in impE, assumption)
apply clarsimp
apply (erule allE, erule impE, rule_tac P="arg_rel _" and Q="¬snd _" in conjI, assumption, assumption)
apply (erule allE, erule allE, erule_tac P="Γ⊢⇩h ⟨_, _⟩ ⇒ _" in impE, assumption)
apply (rename_tac s s' n ret)
apply (case_tac ret; (simp; fail)?)
apply (clarsimp simp: in_liftE[simplified liftE_def])
apply (erule allE, erule allE, erule_tac P="_ ∈ fst _" in impE, assumption)
apply (auto simp: unif_rrel_def)

We will try to compact this proof using the simplification and classical logic methods. The most commonly used are clarsimpforce, fastforce and auto, which are all part of the "clasimp" toolbox. These methods can subsume:

  • simp: by default
  • druledest attribute
  • erule: elim attribute
  • rule: intro attribute
  • case_tac: split attribute

When working with a sequential forward-deduction proof such as this, it's often a good idea to start from the end. First, let's see if the classical-simp methods can solve the last forward step:

  apply (clarsimp simp: in_liftE[simplified liftE_def])
+ try0 simp: unif_rrel_def
- apply (erule allE, erule allE, erule_tac P="_ ∈ fst _" in impE, assumption)
- apply (auto simp: unif_rrel_def)

We immediately get

  try0 simp: unif_rrel_def
+ by (fastforce simp: unif_rrel_def)

and even

  apply (case_tac ret; (simp; fail)?)
- apply (clarsimp simp: in_liftE[simplified liftE_def])
- try0 simp: unif_rrel_def
- by (fastforce simp: unif_rrel_def)
+ by (fastforce simp: unif_rrel_def in_liftE[simplified liftE_def])

The case_tac can often be replaced with a split rule. In this case, ret has type xstate, so we use xstate.splits:

  apply (rename_tac s s' n ret)
-  apply (case_tac ret; (simp; fail)?)
-  by (fastforce simp: unif_rrel_def in_liftE[simplified liftE_def])
+  by (fastforce simp: unif_rrel_def in_liftE[simplified liftE_def] split: xstate.splits)

Now that our proof does not need the names, we can also remove the rename_tac.

At this point, we have the forward instantiations

  apply (erule allE, erule impE, rule_tac P="arg_rel _" and Q="¬snd _" in conjI, assumption, assumption)
  apply (erule allE, erule allE, erule_tac P="Γ⊢⇩h ⟨_, _⟩ ⇒ _" in impE, assumption)

the original proof added explicit terms to document what was being instantiated. However, looking at the proof state and the assumption steps, we can conclude that the instantiations in this proof are rather trivial and do not need documentation. fastforce has no problem doing them automatically:

  apply (clarsimp simp: ccorres_underlying_def corres_underlying_def rf_sr_def Ball_def liftE_def)
- apply (erule allE, erule allE, erule_tac P="cstate_relation _ _" in impE, assumption)
- apply clarsimp
- apply (erule allE, erule impE, rule_tac P="arg_rel _" and Q="¬snd _" in conjI, assumption, assumption)
- apply (erule allE, erule allE, erule_tac P="Γ⊢⇩h ⟨_, _⟩ ⇒ _" in impE, assumption)
+ by (fastforce simp: unif_rrel_def in_liftE[simplified liftE_def] split: xstate.splits)

If we want, we can go all the way and convert this proof to a one-liner:

- apply (drule ac_corres_ccorres_underlying)
- apply (clarsimp simp: ccorres_underlying_def corres_underlying_def rf_sr_def Ball_def liftE_def)
- by (fastforce simp: unif_rrel_def in_liftE[simplified liftE_def] split: xstate.splits)
+ by (fastforce simp: ccorres_underlying_def corres_underlying_def rf_sr_def Ball_def liftE_def
+                     unif_rrel_def in_liftE[simplified liftE_def]
+               split: xstate.splits dest!: ac_corres_ccorres_underlying)