Note: This is really just a new version of an exercise page provided by Laboratory for Applied Logic.
HOL can prove a great many things for us automatically, but it can't prove everything. Therefore we are going to have to learn how to do proofs for ourselves.
In the last exercise we proved that a CMOS implementation of a NOT gate was correct. This time we will try something a little easier. We begin by setting a suitably easy goal, do this by entering the following to HOL.
g `T /\ (!t. F ==> t)`;
In response to this HOL will reply with the following.
> val it =
Proof manager status: 1 proof.
1. Incomplete:
Initial goal:
``T /\ (!t. F ==> t)``
: GoalstackPure.proofs
This tells us that HOL is currently managing one proof attempt, and tells us what remains to be proved for that attempt.
Your task now, and at each step of a proof, is to select a tactic to expand on the current goal. The idea is to choose a tactic which will either solve the goal, or break it down into one or more simpler goals that will be easier for subsequent tactics to solve. The hard part is deciding which tactic to expand. The sad truth here is that the only way to acquire this knowledge is through practice and browsing the documentation. Lets start with the practice.
The goal is a conjunction, that is, something of the form
A /\ B. Such a goal can
be reduced to the simpler problem of separately proving
A and proving B. We
can do this by expanding the tactic CONJ_TAC on the goal.
- e CONJ_TAC;
OK..
2 subgoals:
> val it =
``!t. F ==> t``
``T``
: GoalstackPure.goalstack
The output from HOL tells us that we have broken the original goal down into two subgoals, and displays those. We now commence work on the new goals, working our way up from the bottom.
The first goal we tackle is dead easy. We have to prove
T, which is an axiom of the logic! The ML identifier
TRUTH is bound to the theorem that asserts T
holds. (Don't believe me, evaluate TRUTH to see for
yourself.) The tactic ACCEPT_TAC theorem will solve the current
goal if theorem asserts that it is true. For
example, we can solve the current goal as follows.
- e (ACCEPT_TAC TRUTH);
OK..
Goal proved.
|- T
Remaining subgoals:
> val it =
``!t. F ==> t``
: GoalstackPure.goalstack
HOL informs us that we have solved the current goal and shows us
what remains to be proved. Note that the brackets in tactic expansion
were necessary, e requires a tactic for its argument and
ACCEPT_TAC is not really a tactic; its a function that
takes a theorem and returns a tactic.
The remaining goal - that false implies anything - is
also an axiom of the HOL logic. The theorem FALSITY
encodes the axiom, so you can complete the proof using
ACCEPT_TAC again, this time with FALSITY as
its argument.
CONJ_TAC and ACCEPT_TAC are all well and
good, but you need a few more tactics in your basic repertoire. You
will need to know how to read the HOL documentation to you can expand
your knowledge of tactics. Most tactics are described in the following
notation.
goal
================
goal1 goal2 ... goaln
Each goal is written in the form A ?- t, which expresses a desire to prove the term t under the assumptions A. For example, the manual entry for CONJ_TAC describes the tactic thus.
A ?- t1 /\ t2
====================== CONJ_TAC
A ?- t1 A ?- t2
This expresses that we can prove t1 /\ t2 under the assumptions A by proving t1 under the assumptions A and t2 under the assumptions A.
The following tactics are the basics of goal-directed proof in HOL. Read the manual entries for each of them.
GEN_TACCONJ_TACDISCH_TACSTRIP_TACGEN_TAC if the the goal looks like
`!x. t[x]'.
CONJ_TAC if the the goal looks like
`t1 /\ t2'.
DISCH_TAC if the the goal looks like
`t1 ==> t2' or `~t'.
RES_TACRES_TAC
treats assumptions for the form `~t' like
`t ==> F'.EQ_TACEQ_TAC when your goal looks like
`t1 = t2',
where t1 and t2
are boolean. If EQ_TAC won't break your goal into two
implications, its probably because the equality is not a
boolean equality. Check your types.EXISTS_TACEXISTS_TAC when your goal has the
form: `?x. t[x]'.REWRITE_TACREWRITE_TAC. There are a number of
variations on rewriting. Follow the links at the bottom of the REWRITE_TAC manual page to find out what they
are.To prove this goal, you might want to useASM_REWRITE_TAC. Do you need to useEXISTS_TAC?
How does this goal differ from the one above? Do you need to use
EXISTS_TAC?
You need use only two tactics to prove this, though you need them more than once.
REPEAT in your proof?
How could you prove this?!a b c. (a = b) /\ (b = c) /\ P(a) ==> P(b) \/ P(c)
This proof needs EQ_TAC.
By now you should have several (hopefully complete) proofs in the proof manager, like the following:
Proof manager status: 4 proofs. 6. Completed: |- T /\ (!t. F ==> t) 5. Completed: |- (!x. P x) ==> (?y. P y) 4. Completed: |- P x ==> (?y. P y) 3. Completed: |- (!x. P x ==> Q x) /\ ~(Q a) ==> ~(P a) 2. Completed: |- !a b c. (b = a) /\ (c = b) /\ P a ==> P b \/ P c 1. Completed: |- (!x. P /\ Q x) = P /\ (!x. Q x)
Every time you start a new proof, you see all the previous
attempted proofs in this session. Use drop() to get rid
of the latest proof attempt. If you want to work on another proof,
use R n (where n
is a number) to Rotate proofs 1 through
n to the bottom of the proof stack.