6

Sometimes, when I’m writing apply-style proofs, I have wanted a way to modify a proof method foo to

Try foo on the first goal. If it solves the goal, good; if it does not solve it, revert to the original state and fail.

This came up in the following code:

qed (subst fibs.simps, (subst fib.simps)?, simp add: nth_zipWith nth_tail)+

After some change further up, the call to simp would not fully solve a goal any more, and then this would loop. If I could have specified something like

qed (solve_goal(subst fibs.simps, (subst fib.simps)?, simp add: nth_zipWith nth_tail))+

or (alternative suggested synatx)

qed ((subst fibs.simps, (subst fib.simps)?, simp add: nth_zipWith nth_tail)!)+

or (maybe even nicer syntax)

qed ((subst fibs.simps, (subst fib.simps)?, simp add: nth_zipWith nth_tail)[1!])+

it would have stopped at the first goal that was not solvable by this script.

John Wickerson
  • 1,204
  • 12
  • 23
Joachim Breitner
  • 25,395
  • 6
  • 78
  • 139
  • I guess your `fibs.simps` or `fib.simps` trigger the looping behaviour (maybe due to general left hand side and an if on the right hand side)? Often it is possible to replace these by conditional rules. – Lars Noschinski Mar 08 '13 at 09:42
  • I submitted a [patch implementing this](https://mailmanbroy.informatik.tu-muenchen.de/pipermail/isabelle-dev/2013-March/003911.html), lets see what happens. – Joachim Breitner Mar 08 '13 at 12:48
  • @Joachim Breitner: Just for the record, personally I don't think that in a structured proof (as your example) such a monstrosity belongs to a `qed` ;). I would always prefer to explicitly set-up another sub-proof inside the corresponding `proof`/`qed`. However, you are talking about `apply` scripts and for them I completely agree. (Maybe you could turn the `qed` in your example into an `apply`?) – chris Mar 15 '13 at 05:58
  • The method that I claled in `proof` created several goals, of which only one was non-trivial; that one was proven in proper isar. The others would be solved by `qed simp` (would that be ok in your opinion?) if `fibs.simps` did not loop. The monstrosity above was the closest to the conceptual `simp` that I would have preferred. I could have used `apply_end`, but I believe that is even less nice. – Joachim Breitner Mar 15 '13 at 22:23

4 Answers4

3

Isabelle has no such combinator, which is something I miss, too. Often, I can avoid the need for such a combinator if I replace auto or simp calls by fastforce or force (which have the solve-or-fail behaviour).

So, if the simp in your example is supposed to solve the goal (without looping),

qed (subst fibs.simps, (subst fib.simps)?, fastforce simp: nth_zipWith nth_tail)+

might be a more robust variant.

Lars Noschinski
  • 3,667
  • 16
  • 29
  • 1
    Ah, finally a reason to use `fastforce` or `force` – I never felt the need for them and were only using `auto` and `simp`. Nevertheless, would it be possible to implement such a combinator with `ML {* .. *}` or is that part not extensible? – Joachim Breitner Mar 08 '13 at 10:11
  • 2
    The Isabelle/Isar method sublanguage can be extended by `method_setup`, but the few method combinators are fixed. So you could define your own proof method based on arbitrary complex tactics and tacticals, but it won't change the stylized manner of proof method expressions. – Makarius Mar 08 '13 at 11:39
3

Since the advent of the Eisbach proof script language, this is now supported. After importing "~~/src/HOL/Eisbach/Eisbach", one can replace

apply foo

with

apply (solves ‹foo›)

and the line will fail if solves produces any new goals. This can be combined with [1] as in

apply (solves ‹(auto)[1]›)

if desired.

The definition of solves is actually quite simple:

method solves methods m = (m; fail)
Joachim Breitner
  • 25,395
  • 6
  • 78
  • 139
1

While there is no built-in tactic or combinator available, you can implement one yourself as follows:

ML {*
fun solved_tac thm =
  if nprems_of thm = 0 then Seq.single thm else Seq.empty
*}

method_setup solved = {*
  Scan.succeed (K (SIMPLE_METHOD solved_tac))
*}

This creates a new method solved that will succeed if the current goal has been completely solved, or fail if there is one or more subgoals still remaining.

It can be used, for example, as follows:

lemma "a ∨ ¬ a "
  apply ((rule disjI1, solved) | (simp, solved))
  done

Without the solved clause, Isabelle will select the rule disjI1 side of the apply step, leaving you with an unsolvable goal. With the solved clause in each side, Isabelle attempts to use rule disjI1 and, when it fails to solve the goal, switches to the simp which is then successful.

This can be used to solve individual goals by using Isabelle's (...)[1] syntax. For example, the following statement will attempt to solve as many subgoals as possible using simp, but will leave a subgoal unchanged if simp fails to completely solve it:

apply ((simp, solved)[1])+
davidg
  • 5,868
  • 2
  • 33
  • 51
  • Great, this allows me to have the desired `method[1!]` (which succeeds if `method` solves the first goal, even if there are other goals left) as `(method, solved)[1]`. – Joachim Breitner Apr 10 '13 at 09:11
  • I think I also started somewhere similar code that would yield an `assert_goals n` method that succeeds iff the number of open goals is n... – Joachim Breitner Apr 10 '13 at 09:14
  • @JoachimBreitner: Thanks. I meant to mention that, but it slipped my mind. I have added an example at the end, which hopefully helps future searchers. – davidg Apr 10 '13 at 23:51
0

The Isar language of Isabelle does not provide that feature; this is intentional and not a bug, as explained on the isabelle developer list:

The Isar proof method language was designed a certain way, to arrive at "stilized" specifications of some operational aspects in the proof text. It has excluded any kind of programming or sophisticated control structures on purpose.

Joachim Breitner
  • 25,395
  • 6
  • 78
  • 139