mergesort mit Zusicherungen
Von: Joachim Durchholz (jo@durchholz.org) [Profil]
Datum: 16.05.2006 11:29
Message-ID: <e4c61s$n2p$1@online.de>
Newsgroup: de.comp.lang.funktional
Datum: 16.05.2006 11:29
Message-ID: <e4c61s$n2p$1@online.de>
Newsgroup: de.comp.lang.funktional
Im Zuge einer anderen Diskussion gab's verschiedene Meinungen darüber, wie nützlich denn Zusicherungen sind. Insbesondere, ob eine Schleifeninvariante (im rekursiven Fall: der Induktionsschritt) für den Programmierer überhaupt handhabbar sind. In diesem Zusammenhang wurde ich gebeten, als einfaches Beispiel einfach mal einen Mergesort mit entsprechenden Zusicherungen hinzuschreiben. Genauer gesagt, muss nur gerade so viel an Zusicherungen hingeschrieben werden, dass ein lediglich konstruktive Logik verwendendes Beweissystem in der Lage ist, aus den Zusicherungen die Nachbedingungen der Sortierfunktion zu beweisen. Damit die Sache nicht zu lang wird, schreibe ich nur die Zusicherungen für "Ergebnis ist sortiert" hin. In einem echten Mergesort müsste man auch nachweisen, dass das Ergebnis die gleichen Elemente wie der Input enthält (und im Fall von Dubletten sogar in der gleichen Anzahl), aber das spar ich mir hier - man bräuchte einiges an Hilfsfunktionen für die Zusicherungen. Den Algorithmus habe ich aus http://www.physik.fu-berlin.de/~aloose/static/informatik/vordiplom/sortieren.txt geklaut, es ist in Haskell (ist die syntaktisch einfachste Sprache). Syntaktisch ergänzt habe ich die Sprache um Vorbedingungen und Nachbedingungen: <Funktionsname> [<Parameterliste>] :: require <boolescher Ausdruck> <Funktionsname> [<Parameterliste>] :: ensure <boolescher Ausdruck> In Nachbedingungen (bei "ensure") steht der Name "result" für das Funktionsergebnis. Erstmal eine Hilfsfunktion: sorted :: Ord a => [a] -> Bool sorted [] = True sorted [x] = True sorted (x1:x2:xs) = (x1 <= x2) && sorted (x2:xs) merge :: Ord a => [a] -> [a] -> [a] merge a b :: require (sorted a) && (sorted b) merge a b :: ensure sorted result -- Diese beiden Zusicherungen reichen dem Inferenzsystem bereits! merge [] [] = [] -- Nachbedingung folgt trivial aus "sorted [] = True". merge [] ys = ys merge xs [] = xs -- Nachbedingung folgt trivial aus der Vorbedingung. merge (x:xs) (y:ys) | x <= y = x : merge xs (y:ys) | otherwise = y : merge (x:xs) ys -- Für die merge-Aufrufe muss die Vorbedingung nachgewiesen werden. -- Für die Parameter (y:ys) und (x:xs) -- folgt sie trivial aus der Vorbedingung. -- Für die Parameter xs und ys muss das System -- sorted x:xs -> sorted xs -- als Hilfsannahme beweisen. Sie folgt allerdings recht einfach -- aus "sorted [x] = True" und -- "sorted [x1:x2:xs] = ... && sorted [x2:xs]". -- Das Inferenzsystem muss an dieser Stelle allerdings "schlau genug" -- sein, "sorted (x:xs)" in "sorted (x1:x2:xs)" zu transformieren, -- so dass der dritte Fall von "sorted" anwendbar wird. Im dümmsten Fall -- wird es alle Transformationen stumpf durchprobieren (das gibt einen -- Algorithmus mit O(2^N)), allerdings denke ich, dass heutzutage die -- meisten Inferenzsysteme schlau genug sind, den Suchraum drastisch -- einzuschränken. Meine Hoffnung (Erwartung, eigentlich) ist, dass wir -- bei etwas landen, das die gleiche Charakteristik wie H-M hat: -- theoretischer worst case ist O(2^N), in der Praxis eher O(N log N). -- -- Man beachte, dass wie durch das bloße Hinschreiben von Vor- und -- Nachbedingung der Induktionsschritt als Axiom ins System eingeführt -- wird. -- Das Inferenzsystem muss sich daher nicht mit vollständiger Induktion -- abplagen, die verbleibenden Aussagen lassen sich mit -- Prädikatenlogik der ersten Stufe behandeln. Und die wiederum ist -- vollständig *und* entscheidbar (im Gegensatz zur Prädikatenlogik -- der zweiten Stufe, die Aussagen enthält, für die man die vollständige -- Induktion oder den Satz vom ausgeschlossenen Dritten über unendlich -- große Mengen benötigt, wenn man sie beweisen will). -- (Einschränkung: entscheidbar ist nur der Beweis zutreffender -- Zusicherungen. Der Beweis, dass eine Zusicherung falsch ist, ist -- unentscheidbar, außer man schränkt sich auf Funktionen mit lediglich -- einem Parameter ein - und schon die Gleichheit hat zwei Parameter. -- Ich glaube nicht, dass das in der Praxis ein großes Problem ist, aber -- es ist natürlich auf jeden Fall eine ärgerliche Einschränkung.) halb:: [a] -> Int halb xs = div (length xs) 2 mergesort :: Ord a => [a] -> [a] mergesort a :: ensure sorted merge a mergesort [] = [] mergesort [x] = [x] -- In den obigen beiden Klauseln ist die Nachbedingung trivial erfüllt. mergesort xs merge (mergesort (take (halb xs) xs)) (mergesort (drop (halb xs) xs)) -- Hier muss nachgewiesen werden, dass die Vorbedingungen der -- "merge"-Aufrufe erfüllt sind (trivial, aus der Nachbedingung -- von "mergesort") und dass die Nachbedingung von "mergesort" -- erfüllt ist (wiederum trivial aus der Nachbedingung von "merge"). Der eigentliche Knackpunkt ist die Klausel merge (x:xs) (y:ys) | x <= y = x : merge xs (y:ys) | otherwise = y : merge (x:xs) ys die auch - nicht ganz zufällig, wie ich meine - der Kernpunkt des Verfahrens ist, an dem die eigentliche Arbeit getan wird. Ich halte es deshalb auch für vertretbar, dem Entwickler des Algorithmus zuzumuten, an dieser Stelle notfalls ein paar extra Hinweise für den Inferenzalgorithmus hinzuschreiben (in diesem Fall also, dass x:xs äquivalent zu x1:x2:xs ist und dass er diese Fälle der "sorted"-Funktion anwenden soll). Damit hilft man nicht nur dem Inferenzalgorithmus auf die Sprünge, sondern auch dem Programmierer, der das Ganze ein paar Jahre später wartet und sich fragt, warum das Ganze eigentlich funktioniert. Das ist natürlich aufwändiger als ein einfaches Abschreiben des Algorithmus aus dem Internet - aber ich denke auch, dass man beim Abschreiben nicht wirklich die Details versteht. (Ich hab den Algorithmus auch erst *vollständig* verstanden, als ich die Inferenz für den Fall "merge (x:xs) (y:ys)" nachvollziehen musste... *jetzt* bin ich zuversichtlich, dass ich auch Variationen des Algorithmus auf Korrektheit prüfen kann, und zwar ohne Beispielfälle im Kopf durchzuspielen. Der Vorteil des Inferenzsystems ist, dass mir dann der Computer bestätigen kann, dass der Algorithmus korrekt ist, ohne dass ich alle Details verstehe.) Zum Glück ist algorithmische Arbeit eher selten. In den allermeisten Fällen ruft man einfach eine bestehende Sortierfunktion auf, iteriert über eine Ergebnismenge aus der Datenbank, usw. - die Vor- und Nachbedingungen derartiger Operationen liegen alle auf Trivialniveau. Aber wenn Chris Okasaki etwas über funktionale Datenstrukturen schreibt, wäre es wirklich nicht schlecht, wenn in den Algorithmen die Schlüsselzusicherungen auch mit drinstünden. Vielleicht sollte man sein Buch nochmal mit Grüße, Jo[ Auf dieses Posting antworten ]
Antworten
- Ingo Menger (16.05.2006 13:09)
- Dirk Thierbach (16.05.2006 13:33)
- Georg Bauhaus (16.05.2006 16:33)
- Anonymous Poster (16.05.2006 17:16)
- Georg Bauhaus (16.05.2006 21:36)
- Joachim Durchholz (16.05.2006 18:01)
- Ingo Menger (17.05.2006 12:39)
- Georg Bauhaus (17.05.2006 11:34)
- Ingo Menger (17.05.2006 14:51)
- Georg Bauhaus (17.05.2006 17:21)
- Anonymous Poster (17.05.2006 20:49)
- Joachim Durchholz (17.05.2006 20:59)
- Ingo Menger (22.05.2006 11:19)
- Bernd Eckenfels (22.05.2006 20:26)
- Joachim Durchholz (23.05.2006 11:57)
- Anonymous Poster (23.05.2006 12:20)
- Ingo Menger (23.05.2006 12:42)
- Anonymous Poster (23.05.2006 15:06)
- Joachim Durchholz (23.05.2006 15:06)
- Georg Bauhaus (23.05.2006 13:02)
- Dirk Thierbach (18.05.2006 08:54)
- Anonymous Poster (18.05.2006 10:06)
- Georg Bauhaus (18.05.2006 14:01)
- Dirk Thierbach (18.05.2006 12:55)
- Anonymous Poster (19.05.2006 00:43)
- Bernd Eckenfels (19.05.2006 00:47)
- Anonymous Poster (19.05.2006 01:20)
- Bernd Eckenfels (19.05.2006 01:26)
- Anonymous Poster (19.05.2006 01:29)
- Georg Bauhaus (19.05.2006 03:22)
- Bernd Eckenfels (19.05.2006 07:52)
- Joachim Durchholz (19.05.2006 17:25)
- Joachim Durchholz (19.05.2006 17:12)
- Dirk Thierbach (16.05.2006 12:16)
- Joachim Durchholz (16.05.2006 18:01)
- Dirk Thierbach (17.05.2006 08:03)
- Joachim Durchholz (17.05.2006 11:45)
- Georg Bauhaus (17.05.2006 11:22)
- Dirk Thierbach (17.05.2006 12:32)
- Joachim Durchholz (17.05.2006 20:51)
- Dirk Thierbach (18.05.2006 08:44)
- Anonymous Poster (18.05.2006 10:05)
- Georg Bauhaus (18.05.2006 14:03)
- Georg Bauhaus (18.05.2006 13:58)
- Anonymous Poster (18.05.2006 14:07)
- Georg Bauhaus (18.05.2006 14:24)
- Bernd Eckenfels (19.05.2006 00:46)
- Florian Weimer (24.06.2006 19:45)
- Anonymous Poster (06.07.2006 13:43)
- Florian Weimer (06.07.2006 22:35)
- Anonymous Poster (07.07.2006 10:17)
- Joachim Durchholz (07.07.2006 13:21)
- Anonymous Poster (07.07.2006 14:21)
- Joachim Durchholz (08.07.2006 09:33)
- Anonymous Poster (10.07.2006 12:24)
- Dirk Thierbach (19.05.2006 08:27)
- Bernd Eckenfels (19.05.2006 11:34)
- Dirk Thierbach (20.05.2006 14:13)
- Georg Bauhaus (20.05.2006 21:43)
- Bernd Eckenfels (21.05.2006 04:31)
- Anonymous Poster (21.05.2006 22:01)
- Dirk Thierbach (22.05.2006 08:34)
- Anonymous Poster (22.05.2006 10:35)
- Georg Bauhaus (19.05.2006 14:34)
- Anonymous Poster (19.05.2006 14:59)
- Georg Bauhaus (19.05.2006 16:48)
- Anonymous Poster (19.05.2006 17:07)
- Georg Bauhaus (19.05.2006 22:08)
- Joachim Durchholz (20.05.2006 11:12)
- Georg Bauhaus (19.05.2006 15:19)
- Dirk Thierbach (20.05.2006 14:28)
- Georg Bauhaus (20.05.2006 20:19)
- Dirk Thierbach (20.05.2006 23:23)
- Georg Bauhaus (21.05.2006 02:14)
- Dirk Thierbach (22.05.2006 08:39)
- Joachim Durchholz (18.05.2006 19:57)
