lcp@105: %% $Id$
lcp@105: \part{Advanced methods}
lcp@105: Before continuing, it might be wise to try some of your own examples in
lcp@105: Isabelle, reinforcing your knowledge of the basic functions.
lcp@105: This paper is merely an introduction to Isabelle. Two other documents
lcp@105: exist:
lcp@105: \begin{itemize}
lcp@105: \item {\em The Isabelle Reference Manual\/} contains information about
lcp@105: most of the facilities of Isabelle, apart from particular object-logics.
lcp@105:
lcp@105: \item {\em Isabelle's Object-Logics\/} describes the various logics
lcp@105: distributed with Isabelle. It also explains how to define new logics in
lcp@105: Isabelle.
lcp@105: \end{itemize}
lcp@105: Look through {\em Isabelle's Object-Logics\/} and try proving some simple
lcp@105: theorems. You probably should begin with first-order logic ({\tt FOL}
lcp@105: or~{\tt LK}). Try working some of the examples provided, and others from
lcp@105: the literature. Set theory~({\tt ZF}) and Constructive Type Theory~({\tt
lcp@105: CTT}) form a richer world for mathematical reasoning and, again, many
lcp@105: examples are in the literature. Higher-order logic~({\tt HOL}) is
lcp@105: Isabelle's most sophisticated logic, because its types and functions are
lcp@105: identified with those of the meta-logic; this may cause difficulties for
lcp@105: beginners.
lcp@105:
lcp@105: Choose a logic that you already understand. Isabelle is a proof
lcp@105: tool, not a teaching tool; if you do not know how to do a particular proof
lcp@105: on paper, then you certainly will not be able to do it on the machine.
lcp@105: Even experienced users plan large proofs on paper.
lcp@105:
lcp@105: We have covered only the bare essentials of Isabelle, but enough to perform
lcp@105: substantial proofs. By occasionally dipping into the {\em Reference
lcp@105: Manual}, you can learn additional tactics, subgoal commands and tacticals.
lcp@105: Isabelle's simplifier and classical theorem prover are
lcp@105: difficult to learn, and can be ignored at first.
lcp@105:
lcp@105:
lcp@105: \section{Deriving rules in Isabelle}
lcp@105: \index{rules!derived}
lcp@105: A mathematical development goes through a progression of stages. Each
lcp@105: stage defines some concepts and derives rules about them. We shall see how
lcp@105: to derive rules, perhaps involving definitions, using Isabelle. The
lcp@105: following section will explain how to declare types, constants, axioms and
lcp@105: definitions.
lcp@105:
lcp@105:
lcp@105: \subsection{Deriving a rule using tactics} \label{deriving-example}
lcp@105: \index{examples!of deriving rules}
lcp@105: The subgoal module supports the derivation of rules. The \ttindex{goal}
lcp@105: command, when supplied a goal of the form $\List{\theta@1; \ldots;
lcp@105: \theta@k} \Imp \phi$, creates $\phi\Imp\phi$ as the initial proof state and
lcp@105: returns a list consisting of the theorems
lcp@105: ${\theta@i\;[\theta@i]}$, for $i=1$, \ldots,~$k$. These assumptions are
lcp@105: also recorded internally, allowing \ttindex{result} to discharge them in the
lcp@105: original order.
lcp@105:
lcp@105: Let us derive $\conj$ elimination~(\S\ref{deriving}) using Isabelle.
lcp@105: Until now, calling \ttindex{goal} has returned an empty list, which we have
lcp@105: thrown away. In this example, the list contains the two premises of the
lcp@105: rule. We bind them to the \ML\ identifiers {\tt major} and {\tt
lcp@105: minor}:\footnote{Some ML compilers will print a message such as {\em
lcp@105: binding not exhaustive}. This warns that {\tt goal} must return a
lcp@105: 2-element list. Otherwise, the pattern-match will fail; ML will
lcp@105: raise exception \ttindex{Match}.}
lcp@105: \begin{ttbox}
lcp@105: val [major,minor] = goal FOL.thy
lcp@105: "[| P&Q; [| P; Q |] ==> R |] ==> R";
lcp@105: {\out Level 0}
lcp@105: {\out R}
lcp@105: {\out 1. R}
lcp@105: {\out val major = "P & Q [P & Q]" : thm}
lcp@105: {\out val minor = "[| P; Q |] ==> R [[| P; Q |] ==> R]" : thm}
lcp@105: \end{ttbox}
lcp@105: Look at the minor premise, recalling that meta-level assumptions are
lcp@105: shown in brackets. Using {\tt minor}, we reduce $R$ to the subgoals
lcp@105: $P$ and~$Q$:
lcp@105: \begin{ttbox}
lcp@105: by (resolve_tac [minor] 1);
lcp@105: {\out Level 1}
lcp@105: {\out R}
lcp@105: {\out 1. P}
lcp@105: {\out 2. Q}
lcp@105: \end{ttbox}
lcp@105: Deviating from~\S\ref{deriving}, we apply $({\conj}E1)$ forwards from the
lcp@105: assumption $P\conj Q$ to obtain the theorem~$P\;[P\conj Q]$.
lcp@105: \begin{ttbox}
lcp@105: major RS conjunct1;
lcp@105: {\out val it = "P [P & Q]" : thm}
lcp@105: \ttbreak
lcp@105: by (resolve_tac [major RS conjunct1] 1);
lcp@105: {\out Level 2}
lcp@105: {\out R}
lcp@105: {\out 1. Q}
lcp@105: \end{ttbox}
lcp@105: Similarly, we solve the subgoal involving~$Q$.
lcp@105: \begin{ttbox}
lcp@105: major RS conjunct2;
lcp@105: {\out val it = "Q [P & Q]" : thm}
lcp@105: by (resolve_tac [major RS conjunct2] 1);
lcp@105: {\out Level 3}
lcp@105: {\out R}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@105: Calling \ttindex{topthm} returns the current proof state as a theorem.
lcp@105: Note that it contains assumptions. Calling \ttindex{result} discharges the
lcp@105: assumptions --- both occurrences of $P\conj Q$ are discharged as one ---
lcp@105: and makes the variables schematic.
lcp@105: \begin{ttbox}
lcp@105: topthm();
lcp@105: {\out val it = "R [P & Q, P & Q, [| P; Q |] ==> R]" : thm}
lcp@105: val conjE = result();
lcp@105: {\out val conjE = "[| ?P & ?Q; [| ?P; ?Q |] ==> ?R |] ==> ?R" : thm}
lcp@105: \end{ttbox}
lcp@105:
lcp@105:
lcp@105: \subsection{Definitions and derived rules} \label{definitions}
lcp@105: \index{rules!derived}
lcp@105: \index{Isabelle!definitions in}
lcp@105: \index{definitions!reasoning about|bold}
lcp@105: Definitions are expressed as meta-level equalities. Let us define negation
lcp@105: and the if-and-only-if connective:
lcp@105: \begin{eqnarray*}
lcp@105: \neg \Var{P} & \equiv & \Var{P}\imp\bot \\
lcp@105: \Var{P}\bimp \Var{Q} & \equiv &
lcp@105: (\Var{P}\imp \Var{Q}) \conj (\Var{Q}\imp \Var{P})
lcp@105: \end{eqnarray*}
lcp@105: \index{rewriting!meta-level|bold}
lcp@105: \index{unfolding|bold}\index{folding|bold}
lcp@105: Isabelle permits {\bf meta-level rewriting} using definitions such as
lcp@105: these. {\bf Unfolding} replaces every instance
lcp@105: of $\neg \Var{P}$ by the corresponding instance of $\Var{P}\imp\bot$. For
lcp@105: example, $\forall x.\neg (P(x)\conj \neg R(x,0))$ unfolds to
lcp@105: \[ \forall x.(P(x)\conj R(x,0)\imp\bot)\imp\bot. \]
lcp@105: {\bf Folding} a definition replaces occurrences of the right-hand side by
lcp@105: the left. The occurrences need not be free in the entire formula.
lcp@105:
lcp@105: \begin{warn}
lcp@105: Isabelle does not distinguish sensible definitions, like $1\equiv Suc(0)$, from
lcp@105: equations like $1\equiv Suc(1)$. However, meta-rewriting fails for
lcp@105: equations like ${f(\Var{x})\equiv g(\Var{x},\Var{y})}$: all variables on
lcp@105: the right-hand side must also be present on the left.
lcp@105: \index{rewriting!meta-level}
lcp@105: \end{warn}
lcp@105:
lcp@105: When you define new concepts, you should derive rules asserting their
lcp@105: abstract properties, and then forget their definitions. This supports
lcp@105: modularity: if you later change the definitions, without affecting their
lcp@105: abstract properties, then most of your proofs will carry through without
lcp@105: change. Indiscriminate unfolding makes a subgoal grow exponentially,
lcp@105: becoming unreadable.
lcp@105:
lcp@105: Taking this point of view, Isabelle does not unfold definitions
lcp@105: automatically during proofs. Rewriting must be explicit and selective.
lcp@105: Isabelle provides tactics and meta-rules for rewriting, and a version of
lcp@105: the {\tt goal} command that unfolds the conclusion and premises of the rule
lcp@105: being derived.
lcp@105:
lcp@105: For example, the intuitionistic definition of negation given above may seem
lcp@105: peculiar. Using Isabelle, we shall derive pleasanter negation rules:
lcp@105: \[ \infer[({\neg}I)]{\neg P}{\infer*{\bot}{[P]}} \qquad
lcp@105: \infer[({\neg}E)]{Q}{\neg P & P} \]
lcp@105: This requires proving the following formulae:
lcp@105: $$ (P\Imp\bot) \Imp \neg P \eqno(\neg I)$$
lcp@105: $$ \List{\neg P; P} \Imp Q. \eqno(\neg E)$$
lcp@105:
lcp@105:
lcp@105: \subsubsection{Deriving the introduction rule}
lcp@105: To derive $(\neg I)$, we may call \ttindex{goal} with the appropriate
lcp@105: formula. Again, {\tt goal} returns a list consisting of the rule's
lcp@105: premises. We bind this list, which contains the one element $P\Imp\bot$,
lcp@105: to the \ML\ identifier {\tt prems}.
lcp@105: \begin{ttbox}
lcp@105: val prems = goal FOL.thy "(P ==> False) ==> ~P";
lcp@105: {\out Level 0}
lcp@105: {\out ~P}
lcp@105: {\out 1. ~P}
lcp@105: {\out val prems = ["P ==> False [P ==> False]"] : thm list}
lcp@105: \end{ttbox}
lcp@105: Calling \ttindex{rewrite_goals_tac} with \ttindex{not_def}, which is the
lcp@105: definition of negation, unfolds that definition in the subgoals. It leaves
lcp@105: the main goal alone.
lcp@105: \begin{ttbox}
lcp@105: not_def;
lcp@105: {\out val it = "~?P == ?P --> False" : thm}
lcp@105: by (rewrite_goals_tac [not_def]);
lcp@105: {\out Level 1}
lcp@105: {\out ~P}
lcp@105: {\out 1. P --> False}
lcp@105: \end{ttbox}
lcp@105: Using \ttindex{impI} and the premise, we reduce subgoal~1 to a triviality:
lcp@105: \begin{ttbox}
lcp@105: by (resolve_tac [impI] 1);
lcp@105: {\out Level 2}
lcp@105: {\out ~P}
lcp@105: {\out 1. P ==> False}
lcp@105: \ttbreak
lcp@105: by (resolve_tac prems 1);
lcp@105: {\out Level 3}
lcp@105: {\out ~P}
lcp@105: {\out 1. P ==> P}
lcp@105: \end{ttbox}
lcp@105: The rest of the proof is routine.
lcp@105: \begin{ttbox}
lcp@105: by (assume_tac 1);
lcp@105: {\out Level 4}
lcp@105: {\out ~P}
lcp@105: {\out No subgoals!}
lcp@105: val notI = result();
lcp@105: {\out val notI = "(?P ==> False) ==> ~?P" : thm}
lcp@105: \end{ttbox}
lcp@105: \indexbold{*notI}
lcp@105:
lcp@105: \medskip
lcp@105: There is a simpler way of conducting this proof. The \ttindex{goalw}
lcp@105: command starts a backward proof, as does \ttindex{goal}, but it also
lcp@105: unfolds definitions:
lcp@105: \begin{ttbox}
lcp@105: val prems = goalw FOL.thy [not_def]
lcp@105: "(P ==> False) ==> ~P";
lcp@105: {\out Level 0}
lcp@105: {\out ~P}
lcp@105: {\out 1. P --> False}
lcp@105: {\out val prems = ["P ==> False [P ==> False]"] : thm list}
lcp@105: \end{ttbox}
lcp@105: The proof continues as above, but without calling \ttindex{rewrite_goals_tac}.
lcp@105:
lcp@105:
lcp@105: \subsubsection{Deriving the elimination rule}
lcp@105: Let us derive $(\neg E)$. The proof follows that of~{\tt conjE}
lcp@105: (\S\ref{deriving-example}), with an additional step to unfold negation in
lcp@105: the major premise. Although the {\tt goalw} command is best for this, let
lcp@105: us try~\ttindex{goal}. As usual, we bind the premises to \ML\ identifiers.
lcp@105: We then apply \ttindex{FalseE}, which stands for~$(\bot E)$:
lcp@105: \begin{ttbox}
lcp@105: val [major,minor] = goal FOL.thy "[| ~P; P |] ==> R";
lcp@105: {\out Level 0}
lcp@105: {\out R}
lcp@105: {\out 1. R}
lcp@105: {\out val major = "~ P [~ P]" : thm}
lcp@105: {\out val minor = "P [P]" : thm}
lcp@105: \ttbreak
lcp@105: by (resolve_tac [FalseE] 1);
lcp@105: {\out Level 1}
lcp@105: {\out R}
lcp@105: {\out 1. False}
lcp@105: \ttbreak
lcp@105: by (resolve_tac [mp] 1);
lcp@105: {\out Level 2}
lcp@105: {\out R}
lcp@105: {\out 1. ?P1 --> False}
lcp@105: {\out 2. ?P1}
lcp@105: \end{ttbox}
lcp@105: For subgoal~1, we transform the major premise from~$\neg P$
lcp@105: to~${P\imp\bot}$. The function \ttindex{rewrite_rule}, given a list of
lcp@105: definitions, unfolds them in a theorem. Rewriting does {\bf not}
lcp@105: affect the theorem's hypothesis, which remains~$\neg P$:
lcp@105: \begin{ttbox}
lcp@105: rewrite_rule [not_def] major;
lcp@105: {\out val it = "P --> False [~P]" : thm}
lcp@105: by (resolve_tac [it] 1);
lcp@105: {\out Level 3}
lcp@105: {\out R}
lcp@105: {\out 1. P}
lcp@105: \end{ttbox}
lcp@105: Now {\tt?P1} has changed to~{\tt P}; we need only use the minor premise:
lcp@105: \begin{ttbox}
lcp@105: by (resolve_tac [minor] 1);
lcp@105: {\out Level 4}
lcp@105: {\out R}
lcp@105: {\out No subgoals!}
lcp@105: val notE = result();
lcp@105: {\out val notE = "[| ~?P; ?P |] ==> ?R" : thm}
lcp@105: \end{ttbox}
lcp@105: \indexbold{*notE}
lcp@105:
lcp@105: \medskip
lcp@105: Again, there is a simpler way of conducting this proof. The
lcp@156: \ttindex{goalw} command unfolds definitions in the premises as well
lcp@105: as the conclusion:
lcp@105: \begin{ttbox}
lcp@105: val [major,minor] = goalw FOL.thy [not_def]
lcp@105: "[| ~P; P |] ==> R";
lcp@105: {\out val major = "P --> False [~ P]" : thm}
lcp@105: {\out val minor = "P [P]" : thm}
lcp@105: \end{ttbox}
lcp@105: Observe the difference in {\tt major}; the premises are now {\bf unfolded}
lcp@105: and we need not call~\ttindex{rewrite_rule}. Incidentally, the four calls
lcp@105: to \ttindex{resolve_tac} above can be collapsed to one, with the help
lcp@105: of~\ttindex{RS}\@:
lcp@105: \begin{ttbox}
lcp@105: minor RS (major RS mp RS FalseE);
lcp@105: {\out val it = "?P [P, ~P]" : thm}
lcp@105: by (resolve_tac [it] 1);
lcp@105: {\out Level 1}
lcp@105: {\out R}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@105:
lcp@105:
lcp@105: \medskip Finally, here is a trick that is sometimes useful. If the goal
lcp@105: has an outermost meta-quantifier, then \ttindex{goal} and \ttindex{goalw}
lcp@105: do not return the rule's premises in the list of theorems. Instead, the
lcp@105: premises become assumptions in subgoal~1:
lcp@105: \begin{ttbox}
lcp@105: goalw FOL.thy [not_def] "!!P R. [| ~P; P |] ==> R";
lcp@105: {\out Level 0}
lcp@105: {\out !!P R. [| ~ P; P |] ==> R}
lcp@105: {\out 1. !!P R. [| P --> False; P |] ==> R}
lcp@105: val it = [] : thm list
lcp@105: \end{ttbox}
lcp@105: The proof continues as before. But instead of referring to \ML\
lcp@105: identifiers, we refer to assumptions using \ttindex{eresolve_tac} or
lcp@105: \ttindex{assume_tac}:
lcp@105: \begin{ttbox}
lcp@105: by (resolve_tac [FalseE] 1);
lcp@105: {\out Level 1}
lcp@105: {\out !!P R. [| ~ P; P |] ==> R}
lcp@105: {\out 1. !!P R. [| P --> False; P |] ==> False}
lcp@105: \ttbreak
lcp@105: by (eresolve_tac [mp] 1);
lcp@105: {\out Level 2}
lcp@105: {\out !!P R. [| ~ P; P |] ==> R}
lcp@105: {\out 1. !!P R. P ==> P}
lcp@105: \ttbreak
lcp@105: by (assume_tac 1);
lcp@105: {\out Level 3}
lcp@105: {\out !!P R. [| ~ P; P |] ==> R}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@105: Calling \ttindex{result} strips the meta-quantifiers, so the resulting
lcp@105: theorem is the same as before.
lcp@105: \begin{ttbox}
lcp@105: val notE = result();
lcp@105: {\out val notE = "[| ~?P; ?P |] ==> ?R" : thm}
lcp@105: \end{ttbox}
lcp@105: Do not use the {\tt!!}\ trick if the premises contain meta-level
lcp@105: connectives, because \ttindex{eresolve_tac} and \ttindex{assume_tac} would
lcp@105: not be able to handle the resulting assumptions. The trick is not suitable
lcp@105: for deriving the introduction rule~$(\neg I)$.
lcp@105:
lcp@105:
lcp@105: \section{Defining theories}
lcp@105: \index{theories!defining|(}
lcp@105: Isabelle makes no distinction between simple extensions of a logic --- like
lcp@105: defining a type~$bool$ with constants~$true$ and~$false$ --- and defining
lcp@105: an entire logic. A theory definition has the form
lcp@105: \begin{ttbox}
lcp@105: \(T\) = \(S@1\) + \(\cdots\) + \(S@n\) +
lcp@105: classes {\it class declarations}
lcp@105: default {\it sort}
lcp@105: types {\it type declarations}
lcp@105: arities {\it arity declarations}
lcp@105: consts {\it constant declarations}
lcp@105: rules {\it rule declarations}
lcp@105: translations {\it translation declarations}
lcp@105: end
lcp@105: ML {\it ML code}
lcp@105: \end{ttbox}
lcp@105: This declares the theory $T$ to extend the existing theories
lcp@105: $S@1$,~\ldots,~$S@n$. It may declare new classes, types, arities
lcp@105: (overloadings of existing types), constants and rules; it can specify the
lcp@105: default sort for type variables. A constant declaration can specify an
lcp@105: associated concrete syntax. The translations section specifies rewrite
lcp@105: rules on abstract syntax trees, for defining notations and abbreviations.
lcp@105: The {\ML} section contains code to perform arbitrary syntactic
lcp@105: transformations. The main declaration forms are discussed below; see {\em
lcp@105: Isabelle's Object-Logics} for full details and examples.
lcp@105:
lcp@105: All the declaration parts can be omitted. In the simplest case, $T$ is
lcp@105: just the union of $S@1$,~\ldots,~$S@n$. New theories always extend one
lcp@105: or more other theories, inheriting their types, constants, syntax, etc.
lcp@105: The theory \ttindexbold{Pure} contains nothing but Isabelle's meta-logic.
lcp@105:
lcp@105: Each theory definition must reside in a separate file, whose name is
lcp@105: determined as follows: the theory name, say {\tt ListFn}, is converted to
lcp@105: lower case and {\tt.thy} is appended, yielding the filename {\tt
lcp@105: listfn.thy}. Isabelle uses this convention to locate the file containing
lcp@105: a given theory; \ttindexbold{use_thy} automatically loads a theory's
lcp@105: parents before loading the theory itself.
lcp@105:
clasohm@109: Calling \ttindexbold{use_thy}~{\tt"}{\it T\/}{\tt"} reads a theory from the
clasohm@109: file {\it t}{\tt.thy}, writes the corresponding {\ML} code to the file
clasohm@109: {\tt.}{\it t}{\tt.thy.ML}, reads the latter file, and deletes it if no errors
clasohm@109: occured. This declares the {\ML} structure~$T$, which contains a component
clasohm@109: {\tt thy} denoting the new theory, a component for each rule, and everything
clasohm@109: declared in {\it ML code}.
lcp@105:
lcp@105: Errors may arise during the translation to {\ML} (say, a misspelled keyword)
lcp@105: or during creation of the new theory (say, a type error in a rule). But if
clasohm@109: all goes well, {\tt use_thy} will finally read the file {\it t}{\tt.ML}, if
lcp@105: it exists. This file typically begins with the {\ML} declaration {\tt
lcp@105: open}~$T$ and contains proofs that refer to the components of~$T$.
lcp@105: Theories can be defined directly by issuing {\ML} declarations to Isabelle,
lcp@105: but the calling sequences are extremely cumbersome.
lcp@105:
lcp@105: If theory~$T$ is later redeclared in order to delete an incorrect rule,
lcp@105: bindings to the old rule may persist. Isabelle ensures that the old and
lcp@105: new versions of~$T$ are not involved in the same proof. Attempting to
lcp@105: combine different versions of~$T$ yields the fatal error
lcp@105: \begin{ttbox}
lcp@105: Attempt to merge different versions of theory: \(T\)
lcp@105: \end{ttbox}
lcp@105:
lcp@105: \subsection{Declaring constants and rules}
lcp@105: \indexbold{constants!declaring}\indexbold{rules!declaring}
lcp@105: Most theories simply declare constants and some rules. The {\bf constant
lcp@105: declaration part} has the form
lcp@105: \begin{ttbox}
lcp@105: consts \(c@1\) :: "\(\tau@1\)"
lcp@105: \vdots
lcp@105: \(c@n\) :: "\(\tau@n\)"
lcp@105: \end{ttbox}
lcp@105: where $c@1$, \ldots, $c@n$ are constants and $\tau@1$, \ldots, $\tau@n$ are
lcp@105: types. Each type {\em must\/} be enclosed in quotation marks. Each
lcp@105: constant must be enclosed in quotation marks unless it is a valid
lcp@105: identifier. To declare $c@1$, \ldots, $c@n$ as constants of type $\tau$,
lcp@105: the $n$ declarations may be abbreviated to a single line:
lcp@105: \begin{ttbox}
lcp@105: \(c@1\), \ldots, \(c@n\) :: "\(\tau\)"
lcp@105: \end{ttbox}
lcp@105: The {\bf rule declaration part} has the form
lcp@105: \begin{ttbox}
lcp@105: rules \(id@1\) "\(rule@1\)"
lcp@105: \vdots
lcp@105: \(id@n\) "\(rule@n\)"
lcp@105: \end{ttbox}
lcp@105: where $id@1$, \ldots, $id@n$ are \ML{} identifiers and $rule@1$, \ldots,
lcp@105: $rule@n$ are expressions of type~$prop$. {\bf Definitions} are rules of
lcp@105: the form $t\equiv u$. Each rule {\em must\/} be enclosed in quotation marks.
lcp@105:
lcp@105: \index{examples!of theories}
lcp@105: This theory extends first-order logic with two constants {\em nand} and
lcp@105: {\em xor}, and two rules defining them:
lcp@105: \begin{ttbox}
lcp@105: Gate = FOL +
lcp@105: consts nand,xor :: "[o,o] => o"
lcp@105: rules nand_def "nand(P,Q) == ~(P & Q)"
lcp@105: xor_def "xor(P,Q) == P & ~Q | ~P & Q"
lcp@105: end
lcp@105: \end{ttbox}
lcp@105:
lcp@105:
lcp@105: \subsection{Declaring type constructors}
lcp@105: \indexbold{type constructors!declaring}\indexbold{arities!declaring}
lcp@105: Types are composed of type variables and {\bf type constructors}. Each
lcp@105: type constructor has a fixed number of argument places. For example,
lcp@105: $list$ is a 1-place type constructor and $nat$ is a 0-place type
lcp@105: constructor.
lcp@105:
lcp@105: The {\bf type declaration part} has the form
lcp@105: \begin{ttbox}
lcp@105: types \(id@1\) \(k@1\)
lcp@105: \vdots
lcp@105: \(id@n\) \(k@n\)
lcp@105: \end{ttbox}
lcp@105: where $id@1$, \ldots, $id@n$ are identifiers and $k@1$, \ldots, $k@n$ are
lcp@105: natural numbers. It declares each $id@i$ as a type constructor with $k@i$
lcp@105: argument places.
lcp@105:
lcp@105: The {\bf arity declaration part} has the form
lcp@105: \begin{ttbox}
lcp@105: arities \(tycon@1\) :: \(arity@1\)
lcp@105: \vdots
lcp@105: \(tycon@n\) :: \(arity@n\)
lcp@105: \end{ttbox}
lcp@105: where $tycon@1$, \ldots, $tycon@n$ are identifiers and $arity@1$, \ldots,
lcp@105: $arity@n$ are arities. Arity declarations add arities to existing
lcp@105: types; they complement type declarations.
lcp@105:
lcp@105: In the simplest case, for an 0-place type constructor, an arity is simply
lcp@105: the type's class. Let us declare a type~$bool$ of class $term$, with
lcp@105: constants $tt$ and~$ff$:\footnote{In first-order logic, booleans are
lcp@105: distinct from formulae, which have type $o::logic$.}
lcp@105: \index{examples!of theories}
lcp@105: \begin{ttbox}
lcp@105: Bool = FOL +
lcp@105: types bool 0
lcp@105: arities bool :: term
lcp@105: consts tt,ff :: "bool"
lcp@105: end
lcp@105: \end{ttbox}
lcp@105: In the general case, type constructors take arguments. Each type
lcp@105: constructor has an {\bf arity} with respect to
lcp@105: classes~(\S\ref{polymorphic}). A $k$-place type constructor may have
lcp@105: arities of the form $(s@1,\ldots,s@k)c$, where $s@1,\ldots,s@n$ are sorts
lcp@105: and $c$ is a class. Each sort specifies a type argument; it has the form
lcp@105: $\{c@1,\ldots,c@m\}$, where $c@1$, \dots,~$c@m$ are classes. Mostly we
lcp@105: deal with singleton sorts, and may abbreviate them by dropping the braces.
lcp@105: The arity declaration $list{::}(term)term$ is short for
lcp@105: $list{::}(\{term\})term$.
lcp@105:
lcp@105: A type constructor may be overloaded (subject to certain conditions) by
lcp@105: appearing in several arity declarations. For instance, the built-in type
lcp@105: constructor~$\To$ has the arity $(logic,logic)logic$; in higher-order
lcp@105: logic, it is declared also to have arity $(term,term)term$.
lcp@105:
lcp@105: Theory {\tt List} declares the 1-place type constructor $list$, gives
lcp@105: it arity $list{::}(term)term$, and declares constants $Nil$ and $Cons$ with
lcp@105: polymorphic types:
lcp@105: \index{examples!of theories}
lcp@105: \begin{ttbox}
lcp@105: List = FOL +
lcp@105: types list 1
lcp@105: arities list :: (term)term
lcp@105: consts Nil :: "'a list"
lcp@105: Cons :: "['a, 'a list] => 'a list"
lcp@105: end
lcp@105: \end{ttbox}
lcp@105: Multiple type and arity declarations may be abbreviated to a single line:
lcp@105: \begin{ttbox}
lcp@105: types \(id@1\), \ldots, \(id@n\) \(k\)
lcp@105: arities \(tycon@1\), \ldots, \(tycon@n\) :: \(arity\)
lcp@105: \end{ttbox}
lcp@105:
lcp@105: \begin{warn}
lcp@105: Arity declarations resemble constant declarations, but there are {\it no\/}
lcp@105: quotation marks! Types and rules must be quoted because the theory
lcp@105: translator passes them verbatim to the {\ML} output file.
lcp@105: \end{warn}
lcp@105:
lcp@105: \subsection{Infixes and Mixfixes}
lcp@105: \indexbold{infix operators}\index{examples!of theories}
lcp@105: The constant declaration part of the theory
lcp@105: \begin{ttbox}
lcp@105: Gate2 = FOL +
lcp@105: consts "~&" :: "[o,o] => o" (infixl 35)
lcp@105: "#" :: "[o,o] => o" (infixl 30)
lcp@105: rules nand_def "P ~& Q == ~(P & Q)"
lcp@105: xor_def "P # Q == P & ~Q | ~P & Q"
lcp@105: end
lcp@105: \end{ttbox}
lcp@105: declares two left-associating infix operators: $\nand$ of precedence~35 and
lcp@105: $\xor$ of precedence~30. Hence $P \xor Q \xor R$ is parsed as $(P\xor
lcp@105: Q) \xor R$ and $P \xor Q \nand R$ as $P \xor (Q \nand R)$. Note the
lcp@105: quotation marks in \verb|"~&"| and \verb|"#"|.
lcp@105:
lcp@105: The constants \hbox{\verb|op ~&|} and \hbox{\verb|op #|} are declared
lcp@105: automatically, just as in \ML. Hence you may write propositions like
lcp@105: \verb|op #(True) == op ~&(True)|, which asserts that the functions $\lambda
lcp@105: Q.True \xor Q$ and $\lambda Q.True \nand Q$ are identical.
lcp@105:
lcp@105: \indexbold{mixfix operators}
lcp@105: {\bf Mixfix} operators may have arbitrary context-free syntaxes. For example
lcp@105: \begin{ttbox}
lcp@105: If :: "[o,o,o] => o" ("if _ then _ else _")
lcp@105: \end{ttbox}
lcp@105: declares a constant $If$ of type $[o,o,o] \To o$ with concrete syntax
lcp@105: $if~P~then~Q~else~R$ instead of $If(P,Q,R)$. Underscores denote argument
lcp@105: positions. Pretty-printing information can be specified in order to
lcp@105: improve the layout of formulae with mixfix operations. For details, see
lcp@105: {\em Isabelle's Object-Logics}.
lcp@105:
lcp@105: Mixfix declarations can be annotated with precedences, just like
lcp@105: infixes. The example above is just a shorthand for
lcp@105: \begin{ttbox}
lcp@105: If :: "[o,o,o] => o" ("if _ then _ else _" [0,0,0] 1000)
lcp@105: \end{ttbox}
lcp@105: The numeric components determine precedences. The list of integers
lcp@105: defines, for each argument position, the minimal precedence an expression
lcp@105: at that position must have. The final integer is the precedence of the
lcp@105: construct itself. In the example above, any argument expression is
lcp@105: acceptable because precedences are non-negative, and conditionals may
lcp@105: appear everywhere because 1000 is the highest precedence. On the other
lcp@105: hand,
lcp@105: \begin{ttbox}
lcp@105: If :: "[o,o,o] => o" ("if _ then _ else _" [100,0,0] 99)
lcp@105: \end{ttbox}
lcp@156: defines concrete syntax for a
lcp@156: conditional whose first argument cannot have the form $if~P~then~Q~else~R$
lcp@156: because it must have a precedence of at least~100. Since expressions put in
lcp@156: parentheses have maximal precedence, we may of course write
lcp@156: \begin{quote}
lcp@156: \it if (if P then Q else R) then S else T
lcp@156: \end{quote}
lcp@156: Conditional expressions can also be written using the constant {\tt If}.
lcp@105:
lcp@105: Binary type constructors, like products and sums, may also be declared as
lcp@105: infixes. The type declaration below introduces a type constructor~$*$ with
lcp@105: infix notation $\alpha*\beta$, together with the mixfix notation
lcp@105: ${<}\_,\_{>}$ for pairs.
lcp@105: \index{examples!of theories}
lcp@105: \begin{ttbox}
lcp@105: Prod = FOL +
lcp@105: types "*" 2 (infixl 20)
lcp@105: arities "*" :: (term,term)term
lcp@105: consts fst :: "'a * 'b => 'a"
lcp@105: snd :: "'a * 'b => 'b"
lcp@105: Pair :: "['a,'b] => 'a * 'b" ("(1<_,/_>)")
lcp@105: rules fst "fst() = a"
lcp@105: snd "snd() = b"
lcp@105: end
lcp@105: \end{ttbox}
lcp@105:
lcp@105: \begin{warn}
lcp@105: The name of the type constructor is~{\tt *} and not {\tt op~*}, as it would
lcp@105: be in the case of an infix constant. Only infix type constructors can have
lcp@105: symbolic names like~{\tt *}. There is no general mixfix syntax for types.
lcp@105: \end{warn}
lcp@105:
lcp@105:
lcp@105: \subsection{Overloading}
lcp@105: \index{overloading}\index{examples!of theories}
lcp@105: The {\bf class declaration part} has the form
lcp@105: \begin{ttbox}
lcp@105: classes \(id@1\) < \(c@1\)
lcp@105: \vdots
lcp@105: \(id@n\) < \(c@n\)
lcp@105: \end{ttbox}
lcp@105: where $id@1$, \ldots, $id@n$ are identifiers and $c@1$, \ldots, $c@n$ are
lcp@105: existing classes. It declares each $id@i$ as a new class, a subclass
lcp@105: of~$c@i$. In the general case, an identifier may be declared to be a
lcp@105: subclass of $k$ existing classes:
lcp@105: \begin{ttbox}
lcp@105: \(id\) < \(c@1\), \ldots, \(c@k\)
lcp@105: \end{ttbox}
lcp@105: Type classes allow constants to be overloaded~(\S\ref{polymorphic}). As an
lcp@105: example, we define the class $arith$ of ``arithmetic'' types with the
lcp@105: constants ${+} :: [\alpha,\alpha]\To \alpha$ and $0,1 :: \alpha$, for
lcp@105: $\alpha{::}arith$. We introduce $arith$ as a subclass of $term$ and add
lcp@105: the three polymorphic constants of this class.
lcp@105: \index{examples!of theories}
lcp@105: \begin{ttbox}
lcp@105: Arith = FOL +
lcp@105: classes arith < term
lcp@105: consts "0" :: "'a::arith" ("0")
lcp@105: "1" :: "'a::arith" ("1")
lcp@105: "+" :: "['a::arith,'a] => 'a" (infixl 60)
lcp@105: end
lcp@105: \end{ttbox}
lcp@105: No rules are declared for these constants: we merely introduce their
lcp@105: names without specifying properties. On the other hand, classes
lcp@105: with rules make it possible to prove {\bf generic} theorems. Such
lcp@105: theorems hold for all instances, all types in that class.
lcp@105:
lcp@105: We can now obtain distinct versions of the constants of $arith$ by
lcp@105: declaring certain types to be of class $arith$. For example, let us
lcp@105: declare the 0-place type constructors $bool$ and $nat$:
lcp@105: \index{examples!of theories}
lcp@105: \begin{ttbox}
lcp@105: BoolNat = Arith +
lcp@105: types bool,nat 0
lcp@105: arities bool,nat :: arith
lcp@105: consts Suc :: "nat=>nat"
lcp@105: rules add0 "0 + n = n::nat"
lcp@105: addS "Suc(m)+n = Suc(m+n)"
lcp@105: nat1 "1 = Suc(0)"
lcp@105: or0l "0 + x = x::bool"
lcp@105: or0r "x + 0 = x::bool"
lcp@105: or1l "1 + x = 1::bool"
lcp@105: or1r "x + 1 = 1::bool"
lcp@105: end
lcp@105: \end{ttbox}
lcp@105: Because $nat$ and $bool$ have class $arith$, we can use $0$, $1$ and $+$ at
lcp@105: either type. The type constraints in the axioms are vital. Without
lcp@105: constraints, the $x$ in $1+x = x$ would have type $\alpha{::}arith$
lcp@105: and the axiom would hold for any type of class $arith$. This would
lcp@105: collapse $nat$:
lcp@105: \[ Suc(1) = Suc(0+1) = Suc(0)+1 = 1+1 = 1! \]
lcp@105: The class $arith$ as defined above is more specific than necessary. Many
lcp@105: types come with a binary operation and identity~(0). On lists,
lcp@105: $+$ could be concatenation and 0 the empty list --- but what is 1? Hence it
lcp@105: may be better to define $+$ and 0 on $arith$ and introduce a separate
lcp@105: class, say $k$, containing~1. Should $k$ be a subclass of $term$ or of
lcp@105: $arith$? This depends on the structure of your theories; the design of an
lcp@105: appropriate class hierarchy may require some experimentation.
lcp@105:
lcp@105: We will now work through a small example of formalized mathematics
lcp@105: demonstrating many of the theory extension features.
lcp@105:
lcp@105:
lcp@105: \subsection{Extending first-order logic with the natural numbers}
lcp@105: \index{examples!of theories}
lcp@105:
lcp@105: The early part of this paper defines a first-order logic, including a
lcp@105: type~$nat$ and the constants $0::nat$ and $Suc::nat\To nat$. Let us
lcp@105: introduce the Peano axioms for mathematical induction and the freeness of
lcp@105: $0$ and~$Suc$:
lcp@105: \[ \vcenter{\infer[(induct)*]{P[n/x]}{P[0/x] & \infer*{P[Suc(x)/x]}{[P]}}}
lcp@105: \qquad \parbox{4.5cm}{provided $x$ is not free in any assumption except~$P$}
lcp@105: \]
lcp@105: \[ \infer[(Suc\_inject)]{m=n}{Suc(m)=Suc(n)} \qquad
lcp@105: \infer[(Suc\_neq\_0)]{R}{Suc(m)=0}
lcp@105: \]
lcp@105: Mathematical induction asserts that $P(n)$ is true, for any $n::nat$,
lcp@105: provided $P(0)$ holds and that $P(x)$ implies $P(Suc(x))$ for all~$x$.
lcp@105: Some authors express the induction step as $\forall x. P(x)\imp P(Suc(x))$.
lcp@105: To avoid making induction require the presence of other connectives, we
lcp@105: formalize mathematical induction as
lcp@105: $$ \List{P(0); \Forall x. P(x)\Imp P(Suc(x))} \Imp P(n). \eqno(induct) $$
lcp@105:
lcp@105: \noindent
lcp@105: Similarly, to avoid expressing the other rules using~$\forall$, $\imp$
lcp@105: and~$\neg$, we take advantage of the meta-logic;\footnote
lcp@105: {On the other hand, the axioms $Suc(m)=Suc(n) \bimp m=n$
lcp@105: and $\neg(Suc(m)=0)$ are logically equivalent to those given, and work
lcp@105: better with Isabelle's simplifier.}
lcp@105: $(Suc\_neq\_0)$ is
lcp@105: an elimination rule for $Suc(m)=0$:
lcp@105: $$ Suc(m)=Suc(n) \Imp m=n \eqno(Suc\_inject) $$
lcp@105: $$ Suc(m)=0 \Imp R \eqno(Suc\_neq\_0) $$
lcp@105:
lcp@105: \noindent
lcp@105: We shall also define a primitive recursion operator, $rec$. Traditionally,
lcp@105: primitive recursion takes a natural number~$a$ and a 2-place function~$f$,
lcp@105: and obeys the equations
lcp@105: \begin{eqnarray*}
lcp@105: rec(0,a,f) & = & a \\
lcp@105: rec(Suc(m),a,f) & = & f(m, rec(m,a,f))
lcp@105: \end{eqnarray*}
lcp@105: Addition, defined by $m+n \equiv rec(m,n,\lambda x\,y.Suc(y))$,
lcp@105: should satisfy
lcp@105: \begin{eqnarray*}
lcp@105: 0+n & = & n \\
lcp@105: Suc(m)+n & = & Suc(m+n)
lcp@105: \end{eqnarray*}
lcp@105: This appears to pose difficulties: first-order logic has no functions.
lcp@105: Following the previous examples, we take advantage of the meta-logic, which
lcp@105: does have functions. We also generalise primitive recursion to be
lcp@105: polymorphic over any type of class~$term$, and declare the addition
lcp@105: function:
lcp@105: \begin{eqnarray*}
lcp@105: rec & :: & [nat, \alpha{::}term, [nat,\alpha]\To\alpha] \To\alpha \\
lcp@105: + & :: & [nat,nat]\To nat
lcp@105: \end{eqnarray*}
lcp@105:
lcp@105:
lcp@105: \subsection{Declaring the theory to Isabelle}
lcp@105: \index{examples!of theories}
lcp@105: Let us create the theory \ttindexbold{Nat} starting from theory~\verb$FOL$,
lcp@105: which contains only classical logic with no natural numbers. We declare
lcp@105: the 0-place type constructor $nat$ and the constants $rec$ and~$Suc$:
lcp@105: \begin{ttbox}
lcp@105: Nat = FOL +
lcp@105: types nat 0
lcp@105: arities nat :: term
lcp@105: consts "0" :: "nat" ("0")
lcp@105: Suc :: "nat=>nat"
lcp@105: rec :: "[nat, 'a, [nat,'a]=>'a] => 'a"
lcp@105: "+" :: "[nat, nat] => nat" (infixl 60)
lcp@105: rules induct "[| P(0); !!x. P(x) ==> P(Suc(x)) |] ==> P(n)"
lcp@105: Suc_inject "Suc(m)=Suc(n) ==> m=n"
lcp@105: Suc_neq_0 "Suc(m)=0 ==> R"
lcp@105: rec_0 "rec(0,a,f) = a"
lcp@105: rec_Suc "rec(Suc(m), a, f) = f(m, rec(m,a,f))"
lcp@105: add_def "m+n == rec(m, n, %x y. Suc(y))"
lcp@105: end
lcp@105: \end{ttbox}
lcp@105: In axiom {\tt add_def}, recall that \verb|%| stands for~$\lambda$.
lcp@105: Opening the \ML\ structure {\tt Nat} permits reference to the axioms by \ML\
lcp@105: identifiers; we may write {\tt induct} instead of {\tt Nat.induct}.
lcp@105: \begin{ttbox}
lcp@105: open Nat;
lcp@105: \end{ttbox}
lcp@105: File {\tt FOL/ex/nat.ML} contains proofs involving this theory of the
lcp@105: natural numbers. As a trivial example, let us derive recursion equations
lcp@105: for \verb$+$. Here is the zero case:
lcp@105: \begin{ttbox}
lcp@105: goalw Nat.thy [add_def] "0+n = n";
lcp@105: {\out Level 0}
lcp@105: {\out 0 + n = n}
lcp@105: {\out 1. rec(0,n,%x y. Suc(y)) = n}
lcp@105: \ttbreak
lcp@105: by (resolve_tac [rec_0] 1);
lcp@105: {\out Level 1}
lcp@105: {\out 0 + n = n}
lcp@105: {\out No subgoals!}
lcp@105: val add_0 = result();
lcp@105: \end{ttbox}
lcp@105: And here is the successor case:
lcp@105: \begin{ttbox}
lcp@105: goalw Nat.thy [add_def] "Suc(m)+n = Suc(m+n)";
lcp@105: {\out Level 0}
lcp@105: {\out Suc(m) + n = Suc(m + n)}
lcp@105: {\out 1. rec(Suc(m),n,%x y. Suc(y)) = Suc(rec(m,n,%x y. Suc(y)))}
lcp@105: \ttbreak
lcp@105: by (resolve_tac [rec_Suc] 1);
lcp@105: {\out Level 1}
lcp@105: {\out Suc(m) + n = Suc(m + n)}
lcp@105: {\out No subgoals!}
lcp@105: val add_Suc = result();
lcp@105: \end{ttbox}
lcp@105: The induction rule raises some complications, which are discussed next.
lcp@105: \index{theories!defining|)}
lcp@105:
lcp@105:
lcp@105: \section{Refinement with explicit instantiation}
lcp@105: \index{refinement!with instantiation|bold}
lcp@105: \index{instantiation!explicit|bold}
lcp@105: In order to employ mathematical induction, we need to refine a subgoal by
lcp@105: the rule~$(induct)$. The conclusion of this rule is $\Var{P}(\Var{n})$,
lcp@105: which is highly ambiguous in higher-order unification. It matches every
lcp@105: way that a formula can be regarded as depending on a subterm of type~$nat$.
lcp@105: To get round this problem, we could make the induction rule conclude
lcp@105: $\forall n.\Var{P}(n)$ --- but putting a subgoal into this form requires
lcp@105: refinement by~$(\forall E)$, which is equally hard!
lcp@105:
lcp@105: The tactic {\tt res_inst_tac}, like {\tt resolve_tac}, refines a subgoal by
lcp@105: a rule. But it also accepts explicit instantiations for the rule's
lcp@105: schematic variables.
lcp@105: \begin{description}
lcp@105: \item[\ttindexbold{res_inst_tac} {\it insts} {\it thm} {\it i}]
lcp@105: instantiates the rule {\it thm} with the instantiations {\it insts}, and
lcp@105: then performs resolution on subgoal~$i$.
lcp@105:
lcp@105: \item[\ttindexbold{eres_inst_tac}]
lcp@105: and \ttindexbold{dres_inst_tac} are similar, but perform elim-resolution
lcp@105: and destruct-resolution, respectively.
lcp@105: \end{description}
lcp@105: The list {\it insts} consists of pairs $[(v@1,e@1), \ldots, (v@n,e@n)]$,
lcp@105: where $v@1$, \ldots, $v@n$ are names of schematic variables in the rule ---
lcp@105: with {\bf no} leading question marks!! --- and $e@1$, \ldots, $e@n$ are
lcp@105: expressions giving their instantiations. The expressions are type-checked
lcp@105: in the context of a particular subgoal: free variables receive the same
lcp@105: types as they have in the subgoal, and parameters may appear. Type
lcp@105: variable instantiations may appear in~{\it insts}, but they are seldom
lcp@105: required: {\tt res_inst_tac} instantiates type variables automatically
lcp@105: whenever the type of~$e@i$ is an instance of the type of~$\Var{v@i}$.
lcp@105:
lcp@105: \subsection{A simple proof by induction}
lcp@105: \index{proof!by induction}\index{examples!of induction}
lcp@105: Let us prove that no natural number~$k$ equals its own successor. To
lcp@105: use~$(induct)$, we instantiate~$\Var{n}$ to~$k$; Isabelle finds a good
lcp@105: instantiation for~$\Var{P}$.
lcp@105: \begin{ttbox}
lcp@105: goal Nat.thy "~ (Suc(k) = k)";
lcp@105: {\out Level 0}
lcp@105: {\out ~Suc(k) = k}
lcp@105: {\out 1. ~Suc(k) = k}
lcp@105: \ttbreak
lcp@105: by (res_inst_tac [("n","k")] induct 1);
lcp@105: {\out Level 1}
lcp@105: {\out ~Suc(k) = k}
lcp@105: {\out 1. ~Suc(0) = 0}
lcp@105: {\out 2. !!x. ~Suc(x) = x ==> ~Suc(Suc(x)) = Suc(x)}
lcp@105: \end{ttbox}
lcp@105: We should check that Isabelle has correctly applied induction. Subgoal~1
lcp@105: is the base case, with $k$ replaced by~0. Subgoal~2 is the inductive step,
lcp@105: with $k$ replaced by~$Suc(x)$ and with an induction hypothesis for~$x$.
lcp@105: The rest of the proof demonstrates~\ttindex{notI}, \ttindex{notE} and the
lcp@105: other rules of~\ttindex{Nat.thy}. The base case holds by~\ttindex{Suc_neq_0}:
lcp@105: \begin{ttbox}
lcp@105: by (resolve_tac [notI] 1);
lcp@105: {\out Level 2}
lcp@105: {\out ~Suc(k) = k}
lcp@105: {\out 1. Suc(0) = 0 ==> False}
lcp@105: {\out 2. !!x. ~Suc(x) = x ==> ~Suc(Suc(x)) = Suc(x)}
lcp@105: \ttbreak
lcp@105: by (eresolve_tac [Suc_neq_0] 1);
lcp@105: {\out Level 3}
lcp@105: {\out ~Suc(k) = k}
lcp@105: {\out 1. !!x. ~Suc(x) = x ==> ~Suc(Suc(x)) = Suc(x)}
lcp@105: \end{ttbox}
lcp@105: The inductive step holds by the contrapositive of~\ttindex{Suc_inject}.
lcp@105: Using the negation rule, we assume $Suc(Suc(x)) = Suc(x)$ and prove $Suc(x)=x$:
lcp@105: \begin{ttbox}
lcp@105: by (resolve_tac [notI] 1);
lcp@105: {\out Level 4}
lcp@105: {\out ~Suc(k) = k}
lcp@105: {\out 1. !!x. [| ~Suc(x) = x; Suc(Suc(x)) = Suc(x) |] ==> False}
lcp@105: \ttbreak
lcp@105: by (eresolve_tac [notE] 1);
lcp@105: {\out Level 5}
lcp@105: {\out ~Suc(k) = k}
lcp@105: {\out 1. !!x. Suc(Suc(x)) = Suc(x) ==> Suc(x) = x}
lcp@105: \ttbreak
lcp@105: by (eresolve_tac [Suc_inject] 1);
lcp@105: {\out Level 6}
lcp@105: {\out ~Suc(k) = k}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@105:
lcp@105:
lcp@105: \subsection{An example of ambiguity in {\tt resolve_tac}}
lcp@105: \index{examples!of induction}\index{unification!higher-order}
lcp@105: If you try the example above, you may observe that {\tt res_inst_tac} is
lcp@105: not actually needed. Almost by chance, \ttindex{resolve_tac} finds the right
lcp@105: instantiation for~$(induct)$ to yield the desired next state. With more
lcp@105: complex formulae, our luck fails.
lcp@105: \begin{ttbox}
lcp@105: goal Nat.thy "(k+m)+n = k+(m+n)";
lcp@105: {\out Level 0}
lcp@105: {\out k + m + n = k + (m + n)}
lcp@105: {\out 1. k + m + n = k + (m + n)}
lcp@105: \ttbreak
lcp@105: by (resolve_tac [induct] 1);
lcp@105: {\out Level 1}
lcp@105: {\out k + m + n = k + (m + n)}
lcp@105: {\out 1. k + m + n = 0}
lcp@105: {\out 2. !!x. k + m + n = x ==> k + m + n = Suc(x)}
lcp@105: \end{ttbox}
lcp@105: This proof requires induction on~$k$. But the 0 in subgoal~1 indicates
lcp@105: that induction has been applied to the term~$k+(m+n)$. The
lcp@105: \ttindex{back} command causes backtracking to an alternative
lcp@105: outcome of the tactic.
lcp@105: \begin{ttbox}
lcp@105: back();
lcp@105: {\out Level 1}
lcp@105: {\out k + m + n = k + (m + n)}
lcp@105: {\out 1. k + m + n = k + 0}
lcp@105: {\out 2. !!x. k + m + n = k + x ==> k + m + n = k + Suc(x)}
lcp@105: \end{ttbox}
lcp@105: Now induction has been applied to~$m+n$. Let us call \ttindex{back}
lcp@105: again.
lcp@105: \begin{ttbox}
lcp@105: back();
lcp@105: {\out Level 1}
lcp@105: {\out k + m + n = k + (m + n)}
lcp@105: {\out 1. k + m + 0 = k + (m + 0)}
lcp@105: {\out 2. !!x. k + m + x = k + (m + x) ==> k + m + Suc(x) = k + (m + Suc(x))}
lcp@105: \end{ttbox}
lcp@105: Now induction has been applied to~$n$. What is the next alternative?
lcp@105: \begin{ttbox}
lcp@105: back();
lcp@105: {\out Level 1}
lcp@105: {\out k + m + n = k + (m + n)}
lcp@105: {\out 1. k + m + n = k + (m + 0)}
lcp@105: {\out 2. !!x. k + m + n = k + (m + x) ==> k + m + n = k + (m + Suc(x))}
lcp@105: \end{ttbox}
lcp@105: Inspecting subgoal~1 reveals that induction has been applied to just the
lcp@105: second occurrence of~$n$. This perfectly legitimate induction is useless
lcp@105: here. The main goal admits fourteen different applications of induction.
lcp@105: The number is exponential in the size of the formula.
lcp@105:
lcp@105: \subsection{Proving that addition is associative}
lcp@105: \index{associativity of addition}
lcp@105: \index{examples!of rewriting}
lcp@105: Let us do the proof properly, using~\ttindex{res_inst_tac}. At the same
lcp@105: time, we shall have a glimpse at Isabelle's rewriting tactics, which are
lcp@105: described in the {\em Reference Manual}.
lcp@105:
lcp@105: \index{rewriting!object-level}
lcp@105: Isabelle's rewriting tactics repeatedly applies equations to a subgoal,
lcp@105: simplifying or proving it. For efficiency, the rewriting rules must be
lcp@105: packaged into a \bfindex{simplification set}. Let us include the equations
lcp@105: for~{\tt add} proved in the previous section, namely $0+n=n$ and ${\tt
lcp@105: Suc}(m)+n={\tt Suc}(m+n)$:
lcp@105: \begin{ttbox}
lcp@105: val add_ss = FOL_ss addrews [add_0, add_Suc];
lcp@105: \end{ttbox}
lcp@105: We state the goal for associativity of addition, and
lcp@105: use \ttindex{res_inst_tac} to invoke induction on~$k$:
lcp@105: \begin{ttbox}
lcp@105: goal Nat.thy "(k+m)+n = k+(m+n)";
lcp@105: {\out Level 0}
lcp@105: {\out k + m + n = k + (m + n)}
lcp@105: {\out 1. k + m + n = k + (m + n)}
lcp@105: \ttbreak
lcp@105: by (res_inst_tac [("n","k")] induct 1);
lcp@105: {\out Level 1}
lcp@105: {\out k + m + n = k + (m + n)}
lcp@105: {\out 1. 0 + m + n = 0 + (m + n)}
lcp@105: {\out 2. !!x. x + m + n = x + (m + n) ==> Suc(x) + m + n = Suc(x) + (m + n)}
lcp@105: \end{ttbox}
lcp@105: The base case holds easily; both sides reduce to $m+n$. The
lcp@105: tactic~\ttindex{simp_tac} rewrites with respect to the given simplification
lcp@105: set, applying the rewrite rules for~{\tt +}:
lcp@105: \begin{ttbox}
lcp@105: by (simp_tac add_ss 1);
lcp@105: {\out Level 2}
lcp@105: {\out k + m + n = k + (m + n)}
lcp@105: {\out 1. !!x. x + m + n = x + (m + n) ==> Suc(x) + m + n = Suc(x) + (m + n)}
lcp@105: \end{ttbox}
lcp@105: The inductive step requires rewriting by the equations for~{\tt add}
lcp@105: together the induction hypothesis, which is also an equation. The
lcp@105: tactic~\ttindex{asm_simp_tac} rewrites using a simplification set and any
lcp@105: useful assumptions:
lcp@105: \begin{ttbox}
lcp@105: by (asm_simp_tac add_ss 1);
lcp@105: {\out Level 3}
lcp@105: {\out k + m + n = k + (m + n)}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@105:
lcp@105:
lcp@105: \section{A {\sc Prolog} interpreter}
lcp@105: \index{Prolog interpreter|bold}
lcp@105: To demonstrate the power of tacticals, let us construct a {\sc Prolog}
lcp@105: interpreter and execute programs involving lists.\footnote{To run these
lcp@105: examples, see the file {\tt FOL/ex/prolog.ML}.} The {\sc Prolog} program
lcp@105: consists of a theory. We declare a type constructor for lists, with an
lcp@105: arity declaration to say that $(\tau)list$ is of class~$term$
lcp@105: provided~$\tau$ is:
lcp@105: \begin{eqnarray*}
lcp@105: list & :: & (term)term
lcp@105: \end{eqnarray*}
lcp@105: We declare four constants: the empty list~$Nil$; the infix list
lcp@105: constructor~{:}; the list concatenation predicate~$app$; the list reverse
lcp@105: predicate~$rev$. (In {\sc Prolog}, functions on lists are expressed as
lcp@105: predicates.)
lcp@105: \begin{eqnarray*}
lcp@105: Nil & :: & \alpha list \\
lcp@105: {:} & :: & [\alpha,\alpha list] \To \alpha list \\
lcp@105: app & :: & [\alpha list,\alpha list,\alpha list] \To o \\
lcp@105: rev & :: & [\alpha list,\alpha list] \To o
lcp@105: \end{eqnarray*}
lcp@105: The predicate $app$ should satisfy the {\sc Prolog}-style rules
lcp@105: \[ {app(Nil,ys,ys)} \qquad
lcp@105: {app(xs,ys,zs) \over app(x:xs, ys, x:zs)} \]
lcp@105: We define the naive version of $rev$, which calls~$app$:
lcp@105: \[ {rev(Nil,Nil)} \qquad
lcp@105: {rev(xs,ys)\quad app(ys, x:Nil, zs) \over
lcp@105: rev(x:xs, zs)}
lcp@105: \]
lcp@105:
lcp@105: \index{examples!of theories}
lcp@105: Theory \ttindex{Prolog} extends first-order logic in order to make use
lcp@105: of the class~$term$ and the type~$o$. The interpreter does not use the
lcp@105: rules of~\ttindex{FOL}.
lcp@105: \begin{ttbox}
lcp@105: Prolog = FOL +
lcp@105: types list 1
lcp@105: arities list :: (term)term
lcp@105: consts Nil :: "'a list"
lcp@105: ":" :: "['a, 'a list]=> 'a list" (infixr 60)
lcp@105: app :: "['a list, 'a list, 'a list] => o"
lcp@105: rev :: "['a list, 'a list] => o"
lcp@105: rules appNil "app(Nil,ys,ys)"
lcp@105: appCons "app(xs,ys,zs) ==> app(x:xs, ys, x:zs)"
lcp@105: revNil "rev(Nil,Nil)"
lcp@105: revCons "[| rev(xs,ys); app(ys,x:Nil,zs) |] ==> rev(x:xs,zs)"
lcp@105: end
lcp@105: \end{ttbox}
lcp@105: \subsection{Simple executions}
lcp@105: Repeated application of the rules solves {\sc Prolog} goals. Let us
lcp@105: append the lists $[a,b,c]$ and~$[d,e]$. As the rules are applied, the
lcp@105: answer builds up in~{\tt ?x}.
lcp@105: \begin{ttbox}
lcp@105: goal Prolog.thy "app(a:b:c:Nil, d:e:Nil, ?x)";
lcp@105: {\out Level 0}
lcp@105: {\out app(a : b : c : Nil, d : e : Nil, ?x)}
lcp@105: {\out 1. app(a : b : c : Nil, d : e : Nil, ?x)}
lcp@105: \ttbreak
lcp@105: by (resolve_tac [appNil,appCons] 1);
lcp@105: {\out Level 1}
lcp@105: {\out app(a : b : c : Nil, d : e : Nil, a : ?zs1)}
lcp@105: {\out 1. app(b : c : Nil, d : e : Nil, ?zs1)}
lcp@105: \ttbreak
lcp@105: by (resolve_tac [appNil,appCons] 1);
lcp@105: {\out Level 2}
lcp@105: {\out app(a : b : c : Nil, d : e : Nil, a : b : ?zs2)}
lcp@105: {\out 1. app(c : Nil, d : e : Nil, ?zs2)}
lcp@105: \end{ttbox}
lcp@105: At this point, the first two elements of the result are~$a$ and~$b$.
lcp@105: \begin{ttbox}
lcp@105: by (resolve_tac [appNil,appCons] 1);
lcp@105: {\out Level 3}
lcp@105: {\out app(a : b : c : Nil, d : e : Nil, a : b : c : ?zs3)}
lcp@105: {\out 1. app(Nil, d : e : Nil, ?zs3)}
lcp@105: \ttbreak
lcp@105: by (resolve_tac [appNil,appCons] 1);
lcp@105: {\out Level 4}
lcp@105: {\out app(a : b : c : Nil, d : e : Nil, a : b : c : d : e : Nil)}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@105:
lcp@105: {\sc Prolog} can run functions backwards. Which list can be appended
lcp@105: with $[c,d]$ to produce $[a,b,c,d]$?
lcp@105: Using \ttindex{REPEAT}, we find the answer at once, $[a,b]$:
lcp@105: \begin{ttbox}
lcp@105: goal Prolog.thy "app(?x, c:d:Nil, a:b:c:d:Nil)";
lcp@105: {\out Level 0}
lcp@105: {\out app(?x, c : d : Nil, a : b : c : d : Nil)}
lcp@105: {\out 1. app(?x, c : d : Nil, a : b : c : d : Nil)}
lcp@105: \ttbreak
lcp@105: by (REPEAT (resolve_tac [appNil,appCons] 1));
lcp@105: {\out Level 1}
lcp@105: {\out app(a : b : Nil, c : d : Nil, a : b : c : d : Nil)}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@105:
lcp@105:
lcp@105: \subsection{Backtracking}
lcp@105: \index{backtracking}
lcp@105: Which lists $x$ and $y$ can be appended to form the list $[a,b,c,d]$?
lcp@105: Using \ttindex{REPEAT} to apply the rules, we quickly find the solution
lcp@105: $x=[]$ and $y=[a,b,c,d]$:
lcp@105: \begin{ttbox}
lcp@105: goal Prolog.thy "app(?x, ?y, a:b:c:d:Nil)";
lcp@105: {\out Level 0}
lcp@105: {\out app(?x, ?y, a : b : c : d : Nil)}
lcp@105: {\out 1. app(?x, ?y, a : b : c : d : Nil)}
lcp@105: \ttbreak
lcp@105: by (REPEAT (resolve_tac [appNil,appCons] 1));
lcp@105: {\out Level 1}
lcp@105: {\out app(Nil, a : b : c : d : Nil, a : b : c : d : Nil)}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@105: The \ttindex{back} command returns the tactic's next outcome,
lcp@105: $x=[a]$ and $y=[b,c,d]$:
lcp@105: \begin{ttbox}
lcp@105: back();
lcp@105: {\out Level 1}
lcp@105: {\out app(a : Nil, b : c : d : Nil, a : b : c : d : Nil)}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@105: The other solutions are generated similarly.
lcp@105: \begin{ttbox}
lcp@105: back();
lcp@105: {\out Level 1}
lcp@105: {\out app(a : b : Nil, c : d : Nil, a : b : c : d : Nil)}
lcp@105: {\out No subgoals!}
lcp@105: \ttbreak
lcp@105: back();
lcp@105: {\out Level 1}
lcp@105: {\out app(a : b : c : Nil, d : Nil, a : b : c : d : Nil)}
lcp@105: {\out No subgoals!}
lcp@105: \ttbreak
lcp@105: back();
lcp@105: {\out Level 1}
lcp@105: {\out app(a : b : c : d : Nil, Nil, a : b : c : d : Nil)}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@105:
lcp@105:
lcp@105: \subsection{Depth-first search}
lcp@105: \index{search!depth-first}
lcp@105: Now let us try $rev$, reversing a list.
lcp@105: Bundle the rules together as the \ML{} identifier {\tt rules}. Naive
lcp@105: reverse requires 120 inferences for this 14-element list, but the tactic
lcp@105: terminates in a few seconds.
lcp@105: \begin{ttbox}
lcp@105: goal Prolog.thy "rev(a:b:c:d:e:f:g:h:i:j:k:l:m:n:Nil, ?w)";
lcp@105: {\out Level 0}
lcp@105: {\out rev(a : b : c : d : e : f : g : h : i : j : k : l : m : n : Nil, ?w)}
lcp@105: {\out 1. rev(a : b : c : d : e : f : g : h : i : j : k : l : m : n : Nil, ?w)}
lcp@105: val rules = [appNil,appCons,revNil,revCons];
lcp@105: \ttbreak
lcp@105: by (REPEAT (resolve_tac rules 1));
lcp@105: {\out Level 1}
lcp@105: {\out rev(a : b : c : d : e : f : g : h : i : j : k : l : m : n : Nil,}
lcp@105: {\out n : m : l : k : j : i : h : g : f : e : d : c : b : a : Nil)}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@105: We may execute $rev$ backwards. This, too, should reverse a list. What
lcp@105: is the reverse of $[a,b,c]$?
lcp@105: \begin{ttbox}
lcp@105: goal Prolog.thy "rev(?x, a:b:c:Nil)";
lcp@105: {\out Level 0}
lcp@105: {\out rev(?x, a : b : c : Nil)}
lcp@105: {\out 1. rev(?x, a : b : c : Nil)}
lcp@105: \ttbreak
lcp@105: by (REPEAT (resolve_tac rules 1));
lcp@105: {\out Level 1}
lcp@105: {\out rev(?x1 : Nil, a : b : c : Nil)}
lcp@105: {\out 1. app(Nil, ?x1 : Nil, a : b : c : Nil)}
lcp@105: \end{ttbox}
lcp@105: The tactic has failed to find a solution! It reached a dead end at
lcp@105: subgoal~1: there is no~$\Var{x1}$ such that [] appended with~$[\Var{x1}]$
lcp@105: equals~$[a,b,c]$. Backtracking explores other outcomes.
lcp@105: \begin{ttbox}
lcp@105: back();
lcp@105: {\out Level 1}
lcp@105: {\out rev(?x1 : a : Nil, a : b : c : Nil)}
lcp@105: {\out 1. app(Nil, ?x1 : Nil, b : c : Nil)}
lcp@105: \end{ttbox}
lcp@105: This too is a dead end, but the next outcome is successful.
lcp@105: \begin{ttbox}
lcp@105: back();
lcp@105: {\out Level 1}
lcp@105: {\out rev(c : b : a : Nil, a : b : c : Nil)}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@105: \ttindex{REPEAT} stops when it cannot continue, regardless of which state
lcp@105: is reached. The tactical \ttindex{DEPTH_FIRST} searches for a satisfactory
lcp@105: state, as specified by an \ML{} predicate. Below,
lcp@105: \ttindex{has_fewer_prems} specifies that the proof state should have no
lcp@105: subgoals.
lcp@105: \begin{ttbox}
lcp@105: val prolog_tac = DEPTH_FIRST (has_fewer_prems 1)
lcp@105: (resolve_tac rules 1);
lcp@105: \end{ttbox}
lcp@105: Since {\sc Prolog} uses depth-first search, this tactic is a (slow!) {\sc
lcp@105: Prolog} interpreter. We return to the start of the proof (using
lcp@105: \ttindex{choplev}), and apply {\tt prolog_tac}:
lcp@105: \begin{ttbox}
lcp@105: choplev 0;
lcp@105: {\out Level 0}
lcp@105: {\out rev(?x, a : b : c : Nil)}
lcp@105: {\out 1. rev(?x, a : b : c : Nil)}
lcp@105: \ttbreak
lcp@105: by (DEPTH_FIRST (has_fewer_prems 1) (resolve_tac rules 1));
lcp@105: {\out Level 1}
lcp@105: {\out rev(c : b : a : Nil, a : b : c : Nil)}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@105: Let us try {\tt prolog_tac} on one more example, containing four unknowns:
lcp@105: \begin{ttbox}
lcp@105: goal Prolog.thy "rev(a:?x:c:?y:Nil, d:?z:b:?u)";
lcp@105: {\out Level 0}
lcp@105: {\out rev(a : ?x : c : ?y : Nil, d : ?z : b : ?u)}
lcp@105: {\out 1. rev(a : ?x : c : ?y : Nil, d : ?z : b : ?u)}
lcp@105: \ttbreak
lcp@105: by prolog_tac;
lcp@105: {\out Level 1}
lcp@105: {\out rev(a : b : c : d : Nil, d : c : b : a : Nil)}
lcp@105: {\out No subgoals!}
lcp@105: \end{ttbox}
lcp@156: Although Isabelle is much slower than a {\sc Prolog} system, Isabelle
lcp@156: tactics can exploit logic programming techniques.
lcp@156: