↑Programmieren in Gallina
Befehl | Erklärung |
---|---|
Locate "/\". | Sucht die Definition des Symbols /\ .
|
About and. | Informiert über den Typ von and .
|
Print and. | Term und Typ von and .
|
Search (_ /\ _ -> _). | Findet unter anderem proj1 und proj2 .
|
Rüchwärtsschließen. Man arbeitet sich also von der Wurzel des Beweisbaums bis zu Blättern durch.
Regel | Taktik |
---|---|
Γ ⊢ ?: A Γ ⊢ ?: B ──────────────────── Γ ⊢ ?: A∧B | split
|
Γ, a: A, b: B ⊢ ?: C ──────────────────── Γ, h: A∧B ⊢ ?: C | destruct h as (a, b)
|
Γ, a: A ⊢ ?: B ────────────── Γ ⊢ ?: A → B | intro a
|
Γ ⊢ f: A → B Γ ⊢ ?: A ──────────────────────── Γ ⊢ ?: B | apply f
|
Γ ⊢ ?: A ────────── Γ ⊢ ?: A∨B | left
|
Γ ⊢ ?: B ────────── Γ ⊢ ?: A∨B | right
|
Regel | Taktik |
---|---|
Γ ⊢ h: A∨B Γ, a: A ⊢ :? Γ, b: B ⊢ :? ────────────────────────────────────────── Γ ⊢ ?: | destruct h as [a | b]
|
Kurzes Beispiel:
───────────────── exact a a: A, b: B ⊢ a: A ───────────────── destruct h as (a, b) h: A∧B ⊢ a: A ────────────────── intro h ⊢ (h ↦ a): A∧B → A
Die Implementierung:
Theorem projl (A B: Prop): A /\ B -> A. Proof. intro h. destruct h as (a, b). exact a. Qed.
Die integrierte Entwicklungsumgebung zeigt hierbei nach jedem Schritt die verbleibenden Ziele und die vorhandenen Antezedenzen an.
Beim Rückwärtsschließen führt man verbleibende Ziele bei einfacheren Teilbeweisen schrittweise auf Unterziele zurück.
Theorem contraposition (A B: Prop): (A -> B) -> (~B -> ~A). Proof. (* unfold not. *) intro h. intro nb. intro a. apply nb. apply h. exact a. Qed.
In der integrierten Entwicklungsumgebung sieht man den folgenden Verlauf der verbleibenden Ziele:
Kontext | Verbleibendes Ziel | Nächste Taktik |
---|---|---|
leer (A, B: Prop )
| (A → B) → ¬B → ¬A | intro h
|
h: A → B | ¬B → ¬A | intro nb
|
h: A → B , nb: ¬B | ¬A | intro a
|
h: A → B , nb: ¬B , a: A | ⊥ | apply nb
|
h: A → B , nb: ¬B , a: A | B | apply h
|
h: A → B , nb: ¬B , a: A | A | exact a
|
Die Taktik assert
ermöglicht es nun, innerhalb des
Rückwärtsschließens abschnittsweise ein Vorwärtsschließen zu vollführen.
Dies geschieht, indem einem neuen Bezeichner ein Term zugewiesen wird,
den man mit den im Kontext vorhandenen Variablen konstruiert. Der
Bezeichner steht daraufhin ebenfalls als Variable im Kontext zur
Verfügung. Der Vorgang läuft also gleichermaßen ab, wie die Zuweisung
in der Programmierung.
Theorem contraposition (A B: Prop): (A -> B) -> (~B -> ~A). Proof. intro h. intro nb. intro a. assert (b := h a). assert (contradiction := nb b). exact contradiction. Qed.
Die Konstruktion des Terms lässt sich auch in einem Rutsch bewältigen:
intro h. intro nb. intro a. exact (nb (h a)).
Oder man gibt auf Anhieb den gesamten Beweisterm an:
exact (fun h nb a => nb (h a)).
Zum Vergleich die Zuweisung:
fun h nb a => let b := h a in nb b
Zu beachten wäre noch, dass assert
die innere
Struktur des notierten Terms vergessen lässt. Es bleibt nur die
Information übrig, dass eine Variable mit einem bestimmten Typ
konstruiert werden konnte. Möchte man den Term beibehalten,
ersetzt man den Befehl assert
schlicht durch
pose
oder set
. Bei set
wird
zusätzlich im Ziel die Substitution jedes Vorkommens der Terms durch
die Variable vorgenommen. Vermittels unfold
lässt sich
die Variable wieder durch den ursprünglichen Term ersetzen.
Theorem contraposition (A B: Prop): (A -> B) -> (~B -> ~A). Proof. intro h. intro nb. intro a. pose (b := h a). pose (contradiction := nb b). unfold b in contradiction. exact contradiction. Qed.
Bisher wurde die Definition einer Funktion in der Form
Definition f (n: nat): nat := 2*n.
notiert. Alternativ dürfen zur Konstruktion eines Terms ebenfalls Taktiken zum Einsatz kommen. Man notiert:
Definition f (n: nat): nat. Proof. assert (y := 2*n). exact y. Defined.
In das Maschinenwerk der Taktiken wurde einige Funktionalität verbaut, die die Erstellung von Beweisen ergonomischer macht.
Die Taktik intros
ermöglicht es, die durch intro
bewerkstelligte Einführung einer Annahme und die mit destruct
bewerkstelligte Zerlegung dieser in einem Zug auszuführen. Hiermit
bleibt erspart, die Annahme mit einer Hilfsvariable benennen zu müssen.
Einfache Form | Kurzform |
---|---|
Goal forall A B: Prop, A /\ B -> B /\ A. Proof. intro A. intro B. intro h. destruct h as (a, b). exact (conj b a). Qed. |
Goal forall A B: Prop, A /\ B -> B /\ A. Proof. intros A B (a, b). exact (conj b a). Qed. |
Außerdem sind destruct
und intros
fähig,
geschachtelte Konstruktionen in einem Zug zu zerlegen. Dadurch wird
nochmals eine Verkürzung und eine Einsparung von Hilfsvariablen erreicht.
Einfache Form | Kurzform |
---|---|
Goal forall A B C: Prop, A /\ B /\ C -> C /\ B /\ A. Proof. intros A B C. intro h. destruct h as (a, hr). destruct hr as (b, c). exact (conj c (conj b a)). Qed. | Goal forall A B C: Prop, A /\ B /\ C -> C /\ B /\ A. Proof. intros A B C. intro h. destruct h as (a, (b, c)). exact (conj c (conj b a)). Qed. |
Weiterhin kann man repeat
vor split
setzen, um Konjunktionen von mehr als zwei Aussagen in einem Rutsch
in Unterziele aufzutrennen.
Einfache Form | Kurzform |
---|---|
Goal forall A B C: Prop, A /\ B /\ C -> C /\ B /\ A. Proof. intros A B C (a, (b, c)). split. * exact c. * split. - exact b. - exact a. Qed. | Goal forall A B C: Prop, A /\ B /\ C -> C /\ B /\ A. Proof. intros A B C (a, (b, c)). repeat split. * exact c. * exact b. * exact a. Qed. |
Unter Umständen tut repeat split
mehr als man möchte.
Man kann die Aufteilung aber auch genau kontrollieren, indem man
repeat split
gegen
split; [| split]
ersetzt. Das Semikolon-Tactical darin bewirkt, dass die Taktik
hinter dem Semikolon auf jedes durch die Taktik vor dem Semikolon
erzeugte Unterziel angewendet wird. Soll es zu jedem der Unterziele
eine eigene Taktik sein, werden diese in eckige Klammern gesetzt
und durch einen senkrechten Strich getrennt. Weil in unserem Fall ein
zusätzliches split
nur auf das zweite Unterziel angewendet
werden soll, entfällt die Taktik vor dem Strich.
Die Taktiken trivial
, auto
und
tauto
erstellen verbleibende Beweisschritte
in bestimmten Situationen automatisch.
Einfache Form | Kurzform |
---|---|
Goal forall A B: Prop, A /\ B -> B /\ A. Proof. intros A B. intro h. destruct h as (a, b). exact (conj b a). Qed. | Goal forall A B: Prop, A /\ B -> B /\ A. Proof. intros A B (a, b). auto. Qed. |
Die Einführung der Allquantifizierung wird rückwärts über
intro x
vorgenommen, das bedeutet soviel wie
»Sei x fest, aber beliebig«. Zu diesem x
dürfen dann allquantifizierte Antezedenzen spezialisiert werden,
womit man Allquantifizierungen beseitigt. Dies geschieht, indem man
die allquantifizierte Aussage, als abhängige Funktion betrachtet,
auf die Variable appliziert.
Beispiel. Der Beweis des Theorems
(∀x. A(x) ∧ B(x)) ⇔ (∀x. A(x)) ∧ (∀x. B(x))
ist gesucht.
Theorem universal_quant_conj (X: Type) (A B: X -> Prop): (forall x, A x /\ B x) <-> (forall x, A x) /\ (forall x, B x). Proof. split. * intro h. split. - intro x. assert (hx := h x). exact (proj1 hx). - intro x. assert (hx := h x). exact (proj2 hx). * intro h. intro x. split. - exact (proj1 h x). - exact (proj2 h x). Qed.
Nochmals der gleiche Beweis, aber mit reinem Rückwärtsschließen formuliert:
Theorem universal_quant_conj (X: Type) (A B: X -> Prop): (forall x, A x /\ B x) <-> (forall x, A x) /\ (forall x, B x). Proof. split. * intro h. split. - intro x. apply h. - intro x. apply h. * intros (hA, hB). intro x. split. - apply hA. - apply hB. Qed.
Anstelle intro x
kann man auch
intro u
schreiben, womit die Unterscheidung
zwischen der gebundenen Variable x und dem Parameter
u, über dem man argumentiert, klargestellt wird.
Beispiel. Der Beweis des Theorems
(∃x. A(x) ∧ B(x)) ⇒ (∃x. A(x)) ∧ (∃x. B(x))
ist gesucht.
Theorem existential_quant_conj (X: Type) (A B: X -> Prop): (exists x, A x /\ B x) -> (exists x, A x) /\ (exists x, B x). Proof. intro h. destruct h as (x, ab). destruct ab as (a, b). split. - exists x. exact a. - exists x. exact b. Qed.