8.3 A Declarative Proof Language
8.3.1 Realization of the Language
Figure 8.1 showed an example of our proof language. In the context of the above discus- sion, our choices are:
• We explicitly support labels and justification hints in the form of by and from. The former refers to a strategy expression, e.g., the invocation of a tactic, and the latter allows the reference of labels that have been introduced before. Justification hints are optional, that is, can be left out by the user, and the order of the labels does not influence the checking process.
• We support both the forward and the backward style of proof. This will be im- portant, as we will extend the proof language to specify proof strategies, which are
< document > ::= < theory >+
< theory > ::= theory < name > (imports < names >)? < theoryitem >+
< theoryitem > ::= < typedecl > | <definition> | <axiom> | <theorem> | <const> < typedecl > ::= newtype < name >
< definition > ::= definition < name > < parserinfo >? < formula > < const > ::= const < formula > < parserinfo >?
< axiom > ::= theorem < name > < formula >
< theorem > ::= theorem < name > < formula > < proof >? < parserinfo > ::= [ < pinfos > ]
< pinfos > ::= < pinfo > | <pinfo> , <pinfos>
< pinfo > ::= left | right | infixr | infixl | prec = <number> Figure 8.3: Ωmega theory language
sometimes expressed in a backward style more easily.
• To keep the language as simple as possible, we do not introduce further language constructs that avoid the explicit labeling of formulas, except special constructs to support reasoning chains involving binary operators, as needed for equality reasoning or inequality chains.
• Assumptions are lazily processed and no connection to the proof state is required. This provides maximal freedom in the structure of the proof scripts.
• We avoid special symbols to simplify parsing. Error recovery starts with the next keyword or after the next newline character of the proof script.
• To keep the language simple to type, we follow the idea in Mizar and do not introduce a proof command for forward steps.
• To support asynchronous checking and error recovery, we transform the proof script to a proof plan, which makes the proof obligations explicit in the form of expansion tasks.
To show the extendability of our language, we will extend the language in Chapter 10 such that proof strategies can also be specified within the proof document.
Basic Theory Management
Proofs are constructed within the context of a mathematical theory, which consists of a signature of types and constants, and axioms. Consequently, we provide constructs to declare new types, definitions, and axioms. Each theory extends the base theory, which provides two base types for Booleans o and individuals ι, and a type constructor → to define function types. All other types are introduced by type definitions. Moreover, the base theory defines the usual logical connectives. Figure 8.3 shows the abstract syntax of Ωmega’s theory language. Definitions introduce new constants based on the existing theory and dynamically extend the parsing facilities of the formula parser. Therefore, they can be attached with parsing information, such as the precedence of the introduced symbol, its associativity, and whether it is written infix, prefix, or postfix. An example of a simple theory is shown in Figure 8.4.
theory simplesets
const in::i->i->o[prec=200,infixl] definition subsetdef[prec=210,infixl]
!A,B. A subset B <=> (!x. x in A => x in B)
definition setequaldef !A,B. A=B <=> (A subset B /\ B subset
A)
definition intersectiondef[prec=230,infixl]
!A,B. x in A intersection B <=> x in A /\ x in B
definition uniondef[prec=220, infixl]
!A,B,x. x in A union B <=> x in A \/ x in B
Figure 8.4: Theory Simple Sets
Within a theory, theorems can be stated and proved using the proof language, whose abstract syntax is shown in Figure 8.5. We subsequently explain each of the constructs in detail and show how it modifies the current proof plan. Since we do not require justification hints at all and allow for arbitrarily large gaps, the verification of a single proof step can become time consuming. In the worst case a complete proof search has to be performed. To obtain adequate response times a given step is worked off in two phases: First, we perform a quick test, where we assume that the given step can be justified by the prover by a single inference application. This can be tested by a simple matching algorithm. If the test succeeds the step is sound, as the inference application is proved to be correct. Only if the quick test fails, a more comprehensive proof search is started. This mechanism tries to find the missing information needed to justify the step by performing a heuristically guided resource bounded search. If it is not able to find a derivation within the given bound, a failure is reported and further user interaction is necessary. Let us point out that our approach means that a given proof script can be checked efficiently provided that it contains enough information.
Justifications
We distinguish two kinds of justifications, atomic justifications and nonatomic justifi- cations. Atomic justifications are the simplest kind of justification and consist of a
< proof > ::= proof < steps > qed
< steps > ::= (< ostep > < steps >)|<cstep>
< ostep > ::= < set >|<assume>|<have>|<cases>|<goal>|<consider> < cstep > ::= (< goal >)+ |ǫ
< by > ::= (by < name >)? | <proof> < from > ::= (from < label > (, < label >)∗)?
< sform > ::= < form > | . <binop> <form>
< assume > ::= assume < form > < steps > thus < form > < by > < from > < have > ::= have? < sform >| <by> <from>
< cases > ::= (case < form >{ <proof> })+ < by > < from >
< goal > ::= subgoal < form > < from > < by >
< set > ::= set < var >=< form > (, < var >=< form >)∗
< consider >::= consider < form > < by > < from >
by annotation and/or a from justification. The by specifies an inference or tactic which shall be used to close the corresponding proof obligation. The from construct acts as fil- ter on the current context Γ and specifies the assumptions to be used for the verification (i.e., all other assumptions are removed by weakening). Given a proof obligation δ and a context Γ containing derived facts and assumptions, a justification
from l1, . . . , ln by exp (8.3)
generates the proof obligation
l1, . . . , ln ⊢ δ if n > 0
Γ⊢ δ otherwise (8.4)
Complex justifications consist of an entire proof that is enclosed by the keywords proof and qed derives the specified fact. Subproofs introduce hierarchies in the proof plan and represent the specified fact at a lower granularity level. Note that there are two possibilities to introduce hierarchies in the proof plan: (i) by creating a new proof tree containing the more detailed proof and connecting it to the more abstract proof via a forest edge, and (ii) by inserting the more detailed proof in the same proof tree using a hierarchical edge. We have implemented the second approach.
Forward Steps
Forward steps are the steps that occur most frequently in declarative proof scripts. They derive a new formula ϕ, possibly labeled with label l, from the current proof context. To keep the proof scripts short, we do not assign a keyword to those steps and classify them internally as have steps. That is, given a proof state Γ⊢ ∆, the proof command
a + 0 = 0 + a (8.5)
results in the new proof situation Γ, a + 0 = 0 + a ⊢ ∆.
Quick Test: The quick test tries to justify the new fact by the application of the inference name to term positions in the formulas with labels l1, . . . , ln in the current task.
As the result of an inference application is already known before the application of the inference, we can take advantage of this information to further restrict possible bindings. In the case that the quick test fails, the justification needs to be expanded, as shown by the dashed arrow below. Otherwise, the step is marked as correct.
Γ⊢ ∆ have l : ϕ Γ, l : ϕ⊢ ∆
Γ⊢ ϕ
Assume
Given a context Γ⊢ ∆, the command
starts a new proof tree, in which the current context is enriched by the assumption l1 : ϕ1, . . . , ln: ϕn:
Γ, ϕ1, . . . , ϕn⊢ ψ (8.7)
For assumptions, there is no quick test, even though it would be possible to integrate a similar functionality as in Mizar in case a connection to the current goal can be established. Subsequent operations corresponding to the assumptions are executed on the new proof tree. Thus, the modification of the overall proof plan is as follows:
Γ⊢ ∆
assume l1 : ϕ1 and . . . and ln: ϕn. . . thus ψ
Γ, l1 : ϕ1∧ . . . ∧ ln : ϕn⇒ ψ ⊢ ∆
Γ, l1 : ϕ1, . . . , ln: ϕn ⊢ ψ
In the proof plan, ϕ denotes the closure of ϕ over new variables that were introduced by the assumptions. Cases The command case ϕ1 thus ψ . . . case ϕn thus ψ (8.8)
introduces a case distinction over n cases. It reduces the task to n new subtasks, where each subtask has an additional assumption corresponding to the case. The proof obligation associated with the proof command is the condition that the case distinction is exhaustive. All cases must derive the same fact ψ.
Γ⊢ ∆ cases c1. . . cn
Γ, c1 ⊢ ∆ . . . Γ, cn⊢ ∆
Γ⊢ c1∨ . . . ∨ cn
Quick Test: The quick test tries to close the proof obligation that the case distinction is exhaustive by checking whether there is an axiom or assumption of the form c1∨ . . . ∨ cn
(possibly permuted). In the case that the quick test fails, the proof plan is modified as shown below, where the dashed line indicates the proof tree that is introduced when expanding the justification.
Abbreviations and Instantiations
An abbreviation let a=t introduces the variable a as an abbreviation for the term t, provided that a does not already occur as a variable in the context. The abbreviation is stored as an additional assumption. The role of abbreviations is to increase the readability of a proof script by reducing its size. Adding an equation a = t with a fresh variable a as premise is conservative in the sense that a proof using the new variable a can be converted in a proof without a by just substituting all occurrences of a by t. Similarly, the command set a=t is defined and instantiates an unbound meta-variable in the proof state with the given term t. Γ⊢ ∆ let a = t Γ, a = t⊢ ∆ Γ⊢ ∆ set a = t Γ, a = t⊢ ∆ There are no quick tests for the let and the set command.
Subgoals:
Given a context Γ⊢ ∆, the command
subgoal ϕ1 . . . subgoal ϕn (8.9)
The command subgoals reduces a goal of a given task to n subgoals, each of which is represented as a new task. In case a goal is reduced to a single subgoal, a ; is used as syntactic sugar to avoid the need to start a new subproof. New assumptions can be introduced using the using construct.
Quick Test: This checks if there is an inference that matches the current goal and has n premises, each of which unifies with one of the specified subgoals.
Γ⊢ ∆ subgoals s1 . . . sn
Γ⊢ s1 . . . Γ⊢ sn
Γ⊢ s1. . . sn⇒ ∆
If there is no inference introducing the subgoals specified by the user within a single step the repair strategy tries to further reduce the goal in the current task, thus introducing further subgoals, until all specified subgoals are found. If a subgoal matches a specified goal, it is not further refined. If all subgoals are found by a sequence of proof steps, these steps are abstracted to a single justification.
Existential Assumptions
The construct consider provides a means to introduce an existential variable based on an existential assumption. It thus corresponds to existential elimination in natural deduction.
Γ⊢ ∆ consider awithϕ
Γ, ϕ[a] ⊢ ∆
Γ⊢ ∃x.ϕ
Quick Test: The quick test checks whether the existential formula occurs as an as- sumption in the current sequent or is available as an axiom in the current theory.
Qed:
The command qed is used to indicate that a task is solved.
Γ⊢ ∆ qed
Γ⊢ ϕ
Quick Test: The quick test checks whether the goal formula ϕ occurs in the context, or whether the symbol false occurs at top-level on the left hand side of the task, or the symbol true occurs at top-level on the right-hand side of a task. A task can also be closed if the inference name is applied and all its premises and conclusions are matched to term positions in the current task.
Equational Reasoning and Binary Relations
In addition to reasoning with assertions, many mathematical proofs involve computations in the form of chains of equalities or inequalities. To simulate such equality chains, we provide an abbreviation “.” in terms that corresponds to the right-hand side of the previous command, which needs to be a binary predicate. Consider for example the following proof, whose corresponding proof script is shown in Figure 8.6.
Theorem. Every linear function f (x) = ax + b(a, b ∈ R, a 6= 0) is continuous at every point x0.
Proof. The claim to be shown is that for every ǫ > 0 there is a δ > 0 such that whenever |x − x0| < δ, then |f(x) − f(x0)| < ǫ. Now, since
|f(x) − f(x0)| = |ax + b − (ax0+ b)| (8.10)
=|ax − ax0| (8.11)
=|a| · |x − x0| (8.12)
it is clear that |x − x0| < ǫ/(|a|) implies |f(x) − f(x0)| < |a| · ǫ/(|a|) = ǫ. Hence, for all
theorem ∃a, b.a 6= 0 ∧ f(x) = ax + b ⇒ cont(f, x0) proof |f(x) − f(x0)| = |ax + b − (ax0+ b)| . =|ax − ax0| . =|a| · |x − x0| assume |x − x0| < ǫ/(|a|) |f(x) − f(x0)| < |a| · ǫ/(|a|) . = ǫ thus |f(x) − f(x0)| < ǫ assume ǫ > 0 let δ = ǫ/|a| δ > 0 thus ∃δ.δ > 0 ∧ |x − x0| < δ ⇒ |f(x) − f(x0)| < ǫ qed
Figure 8.6: Realization of the example proof in the proof language