Verifikation von funktionalen Programmen

Werbung
Verifikation von funktionalen Programmen
Funktionale Programmiersprachen sind mathematisch orientiert. Auf dieser Basis können
Programme mit wohlbekannten mathematischen Methoden betrachtet werden.
In diesem Zusammenhang existieren Möglichkeiten zur
• Verifikation von Programmeigenschaften (mithilfe von Induktionsprinzipien für
rekursive Funktionen und Datentypen) sowie zur
• Herleitung optimierter rekursiver Funktionsdefinitionen (Unfold/Fold-Verfahren,
Bird/Meertens-Formalismus).
Für die Menge der natürlichen Zahlen ` gelten die Beweisprinzipien der vollständigen
Induktion, mit denen sich Eigenschaften für alle natürlichen Zahlen nachweisen lassen. Mit
dem Induktionsprinzip kann man insbesondere die Korrektheit rekursiver
Funktionsdefinitionen zeigen. Das Induktionsprinzip selbst basiert dabei auf der induktiven
Definition der natürlichen Zahlen.
Induktionsbeweise lassen sich auch auf anderen induktiv definierten Strukturen führen. Ein
Induktionsprinzip allgemein für Datentypen ist die strukturelle Induktion.
Mithilfe einer weiteren Verallgemeinerung, der wohlfundierten Induktion, können Beweise
für beliebige Strukturen, auf denen eine Relation (mit bestimmter Eigenschaft) definiert ist,
geführt werden.
Definition: Natürliche Zahlen
Aus der Definition resultiert das Beweisprinzip der vollständigen Induktion.
Beweisprinzip: Vollständige Induktion
Zum Beweis einer Eigenschaft P, die für alle n ∈ ` gelten soll (∀n ∈ ` .P(n)), reicht es zu
zeigen:
1. Induktionsanfang: Es gilt P(0)
2. Induktionsschluß: Für alle n ∈ ` folgt aus P(n) die Gültigkeit von P(succ(n)).
Eine implizite Voraussetzung ist dabei, daß P für alle n ∈ ` definiert ist.
Definition: Menge der endlichen Listen
Analog zum Prinzip der vollständigen Induktion für natürliche Zahlen wird das Prinzip der
Listeninduktion zum Beweis von Aussagen über endliche Listen betrachtet. Die Menge aller
endlichen Listen über einem Grundtyp a wird wie folgt definiert:
Beweisprinzip: Listeninduktion
Sei nun P eine Eigenschaft von Listen, die unabhängig von den einzelnen Listenelementen
und für alle Listen definiert ist. Dann gilt P für alle endlichen Listen, falls
1. Induktionsanfang: P([]) gilt
2. Induktionsschluß: Für alle Elemente x :: a und ale Listen xs :: [a] gilt P(xs) impliziert
P(x : xs).
Beispiel:
reverse: 1. Version
reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
1
reverse: 2. Version
rev :: [a] -> [a]
rev xs = aux xs []
aux :: [a] -> [a] -> [a]
aux [] ys = ys
aux (x : xs) ys = aux xs (x : ys)
Sind beide Versionen gleichwertig, d.h. gilt für alle endlichen Listen liste:
reverse liste = rev liste?
Lemma 1: Assoziativität der Listenverkettung
(++) :: [a] -> [a] -> [a]
[] ++ ys = ys
(x : xs) ++ ys = x : (xs ++ ys)
Die Funktion (++) ist assozativ, d.h. für beliebige endliche Listen xs, ys, zs :: [a] gilt:
xs ++ (ys ++ zs) = (xs ++ ys) ++ zs.
Beweis: Lemma 1
Induktionsanfang: xs = []
[] ++ (ys ++ zs) = ys ++ zs
= ([] ++ ys) ++ zs
Induktionsschluß: xs = x : xs’
(x : xs’) ++ (ys ++ zs) = x : (xs’ ++ (ys ++ zs))
= x : ((xs’ ++ ys) ++ zs)
= (x : (xs’ ++ ys)) ++ zs
= ((x : xs’) ++ ys) ++ zs
Lemma 2: reverse ist zu sich selbst invers
Die Funktion reverse ist zu sich selbst invers, d.h. für jede endliche Liste xs :: [a] gilt:
reverse (reverse xs) = xs
Dabei sei reverse nach 1. Version definiert!
Beweis: Lemma 2
Induktionsanfang: xs = []
reverse (reverse [] ) = reverse [] = []
Induktionsschluß: xs = x : xs’
reverse (reverse (x : xs’)) =
reverse (reverse xs’ ++ [x]) ?
Zusammenhang Listenkonkatenation und Listenspiegelung
Für alle y :: a und ys :: [a] gilt:
reverse (ys ++ [y]) = (y : reverse ys)
Beweis :
Induktionsanfang: ys = []
reverse ([] ++ [y]) = reverse [y]
= reverse [] ++ [y]
= [] ++ [y]
= [y]
= y : reverse []
Induktionsschluß: ys = z : zs
reverse ((z : zs) ++ [y]) = reverse (z : (zs ++ [y]))
= reverse (zs ++ [y]) ++ [z]
2
= (y : reverse zs) ++ [z]
= y : (reverse zs ++ [z])
= y : reverse (z : zs)
Einsatz im Beweis für Lemma 2:
reverse (reverse (x : xs’))
= reverse (reverse xs’ ++ [x])
= x : reverse (reverse xs’)
= x : xs’
Lemma 3:
Für alle endlichen Listen xs :: [a] gilt:
reverse xs = rev xs
Zunächst gilt für alle xs, ys :: [a]:
aux xs ys = reverse xs ++ ys
Beweis Lemma 3:
Induktionsanfang: xs = []
aux [] ys = ys
= [] ++ ys
= reverse [] ++ ys
Induktionsschluß: xs = x : xs’
aux (x : xs’) ys = aux xs’ (x : ys)
= reverse xs’ ++ (x : ys)
= reverse xs’ ++ ([x] ++ ys)
= (reverse xs’ ++ [x]) ++ ys
= reverse (x : xs’) ++ ys
Daraus folgt :
rev xs = aux xs [] = reverse xs ++ []
= reverse xs
Definition: Strukturelle Induktion
Übertragung des Prinzips der Listeninduktion auf andere algebraische Datenstrukturen.
Allgemein spricht man dann von struktureller Induktion.
Beweisprinzip: Strukturelle Induktion
Beispiel:
Der Datentyp Tree a definiere einen Binärbaum
data Tree a = Leaf a | Node (Tree a) (Tree a).
Induktionsprinzip:
1. Zunächst wird gezeigt, daß die Eigenschaft für Bäume der Form Leaf a gilt.
2. Danach wird gezeigt, daß aus der Gültigkeit für die Bäume l und r die Gültigkeit der
Eigenschaft für den Baum Node l r folgt. Annahme: für l und r gilt die Eigenschaft.
Nachweis von Programmeigenschaften:
Insbesondere über Listen und anderen strukturierten Datenobjekten sind Funktionen höherer
Ordnung zur Definition allgemeiner Schemata der Listenverarbeitung einsetzbar.
3
In der Listenverarbeitung unterscheidet man drei Grundtypen:
• Anwendung einer Transformation auf alle Listenelemente, z.B. alle Listenelemente
verdoppeln, inkrementieren, quadrieren, codieren.
• Faltung von Listen, z.B. Aufaddieren, Maximumsbestimmung.
• Filtern von Listen.
filter :: (a -> Bool) -> [a] -> [a]
filter p [] = []
filter p = (x : xs) if (p x) then x : filter p xs else filter p xs
oder
filter p xs = [x | x <- xs, p x]
Analog zu den Beweisen von Eigenschaften von Funktionen erster Ordnung erfolgen Beweise
für Funktionen höherer Ordnung. Eigenschaften wie die folgenden sind insbesondere im
Kontext von Programmtransformationen hilfreich:
1. map (f.g) = (map f) . (map g)
2. filter p . map f = (map f . filter (p . f))
3. foldr f e (xs ++ ys) = f (foldr f e xs) (foldr f e ys)
Beispiel: Beweis höherer Funktionen für beliebige Listenargumente
Induktionsanfang:
(filter p . map f) [] = filter p (map f [])´
= filter p []
= []
(map f . filter (p . f)) [] = map f (filter (p . f) [] )
= map f []
= []
Induktionsschluß:
(filter p . map f) (x : xs’) = filter p (map f (x : xs’))
= filter p ((f x) : map f xs’)
Fallunterscheidung:
p (f x) hat den Wert True
= filter p ((f x) : map f xs’)
= (f x) : filter p (map f xs’)
= (f x) : (map f (filter (p . f) xs’))
= map f (x : (filter (p . f) xs’))
= (map f . filter (p . f)) (x : xs’)
Für den Fall, daß p (f x) den Wert False hat, erfolgt der Nachweis analog. Falls p (f x) nicht
definiert sein sollte, gilt die Gleichheit, denn in diesem Fall wären beide Ausdrücke auch
nicht definiert.
4
Definition: Wohlfundierte Induktion
Alle zuvor beschriebenen Induktionsprinzipien haben Ordnungseigenschaften ihrer
zugrundeliegenden Bereiche ausgenutzt. Bei den natürlichen Zahlen war das die < Relation
(für P wird im Induktionsschritt von der Gültigkeit von n auf die Gültigkeit für n+1
geschlossenen, wobei n < n+1 gilt). Bei der strukturellen Induktion wurde von einfacheren
Termen auf komplexere Terme geschlossen (eine entsprechende Ordnung wird über die
Größe der Terme definiert).
Bei den meisten Induktionsprinzipien handelt es sich um eine Instanz des sehr allgemeinen
Prinzips der wohlfundierten Induktion. Das Prinzip bezieht sich in seiner Basis auf das
Konzept der wohlfundierten Relation.
Prinzip der strukturellen Induktion für Listen:
Um zu beweisen, daß die logische Eigenschaft P(xs) gilt für jede beliebige endliche Liste xs,
müssen zwei Dinge gezeigt werden:
Basisfall:
Zeige P([]) direkt.
Induktionsschritt:
Zeige P(x:xs) durch Verwenden der Annahme, daß P(xs) gilt.
Mit anderen Worten P(xs) ⇒ P(x:xs) muß gezeigt werden.
P(xs) wird als die Induktionsannahme bezeichnet, da es als richtig angenommen wird, um
P(x:xs) zu zeigen.
Beispiel:
sum :: [Int] -> Int
sum [] = 0
sum (x:xs) = x + sum xs
Für [] wird direkt ein Wert angegeben, und der Wert für sum(x.xs) wird definiert durch den
wert sum xs.
Beispiel:
doubleAll [] = []
doubleAll (z:zs) = 2*z : doubleAll zs
5
Aufgabe: Zu zeigen:
sum(doubleAll xs) = 2 * sum xs
für alle endlichen Listen xs.
Lösung:
Induktionsbasis:
sum(doubleAll []) = 2 * sum []
Induktionsschritt:
sum(doubleAll (x:xs)) = 2 * sum (x:xs)
Zu zeigen unter Verwendung der Induktionsannahme: sum(doubleAll xs) = 2 * sum xs
Induktionsbasis:
linke Seite:
sum(doubleAll [])
= sum []
=0
rechte Seite:
2 * sum []
=2*0
=0
Beide Seiten sind gleich, also gilt die Induktionsbasis.
Induktionsschritt:
linke Seite:
sum (doubleAll (x:xs))
= sum (2*x : doubleAll xs)
= 2*x + sum (doubleAll xs)
rechte Seite:
2*sum(x:xs)
= 2* (x + sum xs)
= 2*x + 2*sum xs
Weitere Vereinfachung der linken Seite unter Verwendung der Induktionsannahme:
sum (doubleAll (x:xs))
= sum (2*x : doubleAll xs)
= 2*x + sum (doubleAll xs)
= 2*x + 2 * sum xs
Beide Seiten stimmen überein. Beweis vollständig.
6
Aufgabe: Zu zeigen:
length (xs ++ ys) = length xs + length ys
Definition: length
length [] = 0
length (z:zs) = 1 + length zs
Definition: ++
[] ++ zs = zs
(w:ws) ++ zs = w : (ws++zs)
Lösung:
Für den Basisfall muß gezeigt werden:
length ([] ++ ys) = length [] + length ys
Und für den Induktionsschritt muß gezeigt werden:
length ((x:xs) ++ ys) = length (x:xs) + length ys
mittels der Induktionsannahme:
length(xs ++ ys) = length xs + length ys
Induktionsbasis:
linke Seite:
length ([] ++ ys)
= length ys
rechte Seite:
length [] + length ys
= 0 + length ys
= length ys
Induktionsschritt:
linke Seite:
length ((x:xs) ++ ys)
= length (x:(xs ++ ys))
= 1 + length (xs ++ ys)
Unter Verwendung der Induktionsannahme:
= 1 + length xs + length ys
rechte Seite:
length (x:xs) + length ys
= 1 + length xs + length ys
Beide Seiten stimmen überein. Beweis vollständig.
7
Aufgabe: Zu zeigen:
reverse (xs ++ ys) = reverse ys ++ reverse xs
Definition reverse:
reverse [] = []
reverse (z:zs) = reverse zs ++ [z]
Lösung:
Induktionsbasis:
reverse ([] ++ ys) = reverse ys ++ reverse []
Induktionsschritt:
reverse ((x:xs) ++ ys) = reverse ys ++ reverse (x:xs)
Induktionsannahme:
reverse (xs ++ ys) = reverse ys ++ reverse xs
Induktionsbasis:
linke Seite:
reverse ([] ++ ys) = reverse ys
rechte Seite:
reverse ys ++ reverse [] = reverse ys ++ []
Wir können nur zeigen, daß die beiden Seiten gleich sind, wenn wir zeigen, daß das
Anhängen einer leeren Liste an das Ende einer Liste eine Identitätsoperation ist.
xs ++ [] = xs
Zu zeigen mittels Induktion über xs.
Induktionsschritt:
linke Seite:
reverse ((x:xs) ++ ys)
= reverse (x:xs ++ ys))
= reverse (xs ++ ys) ++ [x]
= (reverse ys ++ reverse xs) ++ [x]
rechte Seite:
reverse ys ++ reverse (x:xs)
= reverse ys ++ (reverse xs ++ [x])
++ ist assoziativ:
xs ++ (ys ++ zs) = (xs ++ ys) ++ zs
Beide Seiten stimmen überein. Beweis vollständig.
8
Aufgabe: Zeigen Sie die Identität qrev y (a++b) = qrev (qrev y a) b durch Induktion
über a.
Definition grev:
qrev x [] = x
qrev x (y : ys) = qrev (y : x) ys
Lösung:
Wir nummerieren die Gleichung in ++ und qrev:
[] ++ b = b
(app1)
(a : as) ++ b = a : (as ++ b)
(app2)
qrev x [] = x
qrev x (y : ys) = qrev (y:x) ys
(qrev1)
(qrev2)
Basisfall: a = []
qrev y ([] ++ b) = qrev y b
(app1)
= qrev (qrev y []) b
(qrev1)
Sei nun a = a : as. Nach Induktionsvoraussetzung ist
qrev y (a++b) = qrev (qrev y q) b
Im Induktionsschritt ist
qrev y ((a:as)++b) = qrev (qrev y (a:as)) b
zu zeigen.
qrev y ((a:as)++b)
= qrev y a:(as++b)
(app2)
= qrev a:y as++b
(qrev2)
= qrev (qrev a:y as) b
(Ind.Hyp.)
= qrev (qrev y (a:as))b
(qrev2)
Betrachten Sie die Gleichungen
qrev x [] = x [] ++ x = (qrev [] []) ++ x
qrev x (y:ys) = qrev (y:x) ys
= (qrev [] ys) ++ (y :x)
= (qrev [] ys) ++ ([y] ++ x)
= (qrev [y] ys) ++ x
= (qrev [] (y :ys)) ++ x
(1)
(2)
(3)
(4)
(5)
(6)
Handelt es sich um einen Beweis? Falls ja: was wird beweisen? Ergänzen Sie
gegebenenfalls den Beweis und kommentieren Sie ihn.
Es handelt sich um einen Induktionsbeweis der Gleichung
qrev x y = qrev [] y ++ x
Die Induktion erfolgt über die Liste y.
Basisfall:
qrev x [] = x
(qrev1)
= [] ++ x
(app1)
= (qrev [] []) ++ x
(qrev1)
Induktionsschritt:
qrev x (y:ys)
= qrev(y:x) ys
(qrev2)
= (qrev [] ys) ++ (y :x)
(Ind.Hyp.)
9
= (qrev [] ys) ++ ([y] ++ x)
= (qrev [y] ys) ++ x
= (qrev [] (y :ys)) ++ x
(*)
(Assoc. ++), (Ind. Hyp.)
(qrev2)
Assoziativität von ++ wurde in der Vorlesung bewiesen. Beweis von (*) mit (app2):
[y] ++ x = (y:[]) ++ x = (y : ([] ++ x) = y : x
10
Herunterladen