Informatik 2 - Konzepte der Programmierung

Werbung
7
Exkurs: Berechenbarkeit
7 · Exkurs: Berechenbarkeit
7.1
Mächtigkeit von Programmiersprachen · 7.1
Mächtigkeit von Programmiersprachen
Aus mathematischer Sicht ist ein Programm eine Funktion, die eine
Eingabe zusammen mit dem Zustand der Maschine vor Start des
Programmes abbildet auf eine Ausgabe und einen neuen Zustand nach
Programmende.
program :: (State, Input) → (State, Output)
Diese Funktion ist nicht unbedingt auf jeder Eingabe definiert (es ist dann
insbesondere eine partielle Funktion):
I
I
Ein Programm kann fehlschlagen, z.B. bei Division durch Null. (Nagut,
das könnte man noch als Zustand auffassen).
Wichtiger: Ein Programm muss nicht terminieren (es kann sich
“aufhängen”). Hier wird also kein definierter Zustand erreicht.
Offensichtliche Frage Was kann man überhaupt berechnen?
Antwort Tatsächlich ist nicht jede Funktion berechenbar!
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
196
7 · Exkurs: Berechenbarkeit
Mächtigkeit von Programmiersprachen · 7.1
Turing-Maschine
Die Turing-Maschine ist eine abstrakte Rechenmaschine26 .
I
von Neumann Computer (z.B. unsere PCs) entsprechen
Turing-Maschinen mit nur endlichem Hauptspeicher.
Definition Berechenbare Funktionen
Genau die Funktionen die von einer Turing-Maschine berechnet werden
können, heißen berechenbar.
I
Eine Programmiersprache heißt turingmächtig, wenn man mit ihr jede
berechenbare Funktion ausdrücken (implementieren) kann.
• Die meisten “Allzweck”-Programmiersprachen sind turingmächtig27 .
Beispiele: java, Haskell, C, Assembler, der λ-Kalkül, Brainfuck, ...
Beweis: Implementation einer Turing-Maschine in der jeweiligen Sprache.
• Praktische Bedeutung: Wir können prinzipiell jede berechenbare Funktion
implementieren.
26 Vorlesung Konzepte der Informatik oder
27 unter der Annahme es stünde unendlich
Michael Grossniklaus · DBIS
Theoretische Informatik
viel Hauptspeicher zur Verfügung!
Informatik 2 · Sommer 2017
197
7 · Exkurs: Berechenbarkeit
Mächtigkeit von Programmiersprachen · 7.1
Anmerkungen
I
Wir machen bei diesen Betrachtungen keine Aussage über Laufzeit und
Speicherbedarf des Programmes.
I
Auch die Einfachheit der Implementierung ist hier nicht relevant.
I
Die Definition von “berechenbar” (cf. Seite 197) ist zwar ausreichend,
aber etwas unbefriedigend, falls wir kein offensichtlich “unberechenbares”
Problem beschreiben können.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
198
7 · Exkurs: Berechenbarkeit
7.2
Das Halteproblem · 7.2
Das Halteproblem
Das Halteproblem ist sowohl praktisch relevant, als auch ein klassisches
Beispiel für eine nicht berechenbare Funktion.
Angenommen wir haben ein Programm p (z.B. in Form von Quellcode),
das eine Eingabe x verarbeitet:
I
1
I
void p( Input x )
...
—in einer fiktiven, z.B. imperativen Sprache
Es wäre schön zu wissen, ob p bei einer bestimmten Eingabe x
terminiert, oder “sich aufhängt”.
• Diese Frage ist das Halteproblem für p(x).
• Ausprobieren ist keine Option. (Warum nicht?)
Frage Kann man ein Programm schreiben, welches entscheiden kann ob ein
übergebenes Programm (durch dessen Analyse) terminiert, welches also
das Halteproblem beantwortet?
Anwort Nein, es ist im Allgemeinen nicht berechenbar, ob ein Programm
hält.
(Im Speziellen, für manche Programme also, ist dies möglich.)
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
199
7 · Exkurs: Berechenbarkeit
Das Halteproblem · 7.2
Beweisskizze
Beweis durch Widerspruch
Angenommen wir hätten ein Programm halts,
1
Bool halts( Sourcecode p, Input x ) { ... }
—in einer fiktiven imperativen Sprache
welches für jedes beliebige Programm p und jede beliebige Eingabe x
berechnen kann, ob das Programm p mit Eingabe x terminiert:
halts(p, x) _ True ⇐⇒ p(x) terminiert
halts(p, x) _ False ⇐⇒ p(x) terminiert nicht
Beispiel Anwendung auf ein terminierendes Programm:
1
void test1( Input x ) {
1
2
3
4
~
print(x);
> test1("hello world")
hello world
> halts(test1, "hello world")
True
Anmerkung
Michael Grossniklaus · DBIS
}
1
2
3
4
> test1(test1)
void test1( Input x ) { print(x); }
> halts(test1, test1)
True
Wir identifizieren hier Programme mit ihrem Quellcode.
Informatik 2 · Sommer 2017
200
7 · Exkurs: Berechenbarkeit
Das Halteproblem · 7.2
Beispiel Anwendung auf ein nicht immer terminierendes Programm:
1
Int test2( Input x ) {
2
2
while( x > 0 ) {
x := 4;
}
3
4
5
6
3
4
5
6
return 42;
7
8
1
}
Erinnerung
Michael Grossniklaus · DBIS
7
> halts(test2, -23)
True
> test2(-23)
42
> halts(test2, 23)
False
> test2(23)
8
... terminiert nicht
Das Programm halts terminiert auf jeden Fall.
Informatik 2 · Sommer 2017
201
7 · Exkurs: Berechenbarkeit
Das Halteproblem · 7.2
Wir konstruieren einen Widerspruch
Erinnerung
I
1
halts(p, x) berechnet, ob p(x) terminiert.
Konstruieren jetzt ein Programm evil wie folgt:
Int evil( Sourcecode p ) {
• halts wendet das übergebene
2
if (halts(p, p)) {
while (True) {} —Endlosschleife
}
3
4
5
6
• evil kann in einer Endlosschleife
hängen bleiben.
return 1;
7
8
Programm p auf p selbst an. Das
hatten wir schon, cf. Seite 200.
}
Frage
⇒
⇒
Terminiert evil für ein gegebenes Programm p?
p(p) terminiert
halts(p, p) _ True
evil(p) terminiert nicht
Michael Grossniklaus · DBIS
⇒
⇒
p(p) terminiert nicht
halts(p, p) _ False
evil(p) terminiert
Informatik 2 · Sommer 2017
202
7 · Exkurs: Berechenbarkeit
Erinnerung
⇔
Das Halteproblem · 7.2
Bis jetzt haben wir ein Programm evil konstruiert:
evil(p) terminiert
1
Int evil( Sourcecode p ) { ... }
p(p) terminiert nicht
I
Jetzt wenden wir evil auf sich selbst an.
I
Terminiert evil(evil)?
Aus der Konstruktion folgt:
evil(evil) terminiert
⇔
evil(evil) terminiert nicht
Ein Widerspruch. Also muss unsere Annahme (die Existenz von halts)
falsch sein.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
203
7 · Exkurs: Berechenbarkeit
Das Halteproblem · 7.2
Anmerkungen
I
Es sieht hier so aus als wäre dieser Beweis unabhängig vom
Rechenmodell. Ein formaler Beweis bezieht sich jedoch z.B. auf die
Turing-Maschine, und verwendet keine “fiktive imperative
Programmiersprache”.
I
Es ist tatsächlich kein mächtigeres Konzept für eine Rechenmaschine
bekannt.
I
Turing-Maschine und der einfache untypisierte λ-Kalkül (den wir bisher
besprochen haben) sind gleich mächtig.
• Auch im λ-Kalkül können wir nicht alles berechnen, ...
• ...aber kein formales System kann mehr berechnen.
• Beweisidee: Im λ-Kalkül eine Turing-Maschine beschreiben, und umgekehrt.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
204
7 · Exkurs: Berechenbarkeit
Konsequenzen · 7.3
Konsequenzen
7.3
Für die Theorie...
I
Es gibt tatsächlich Funktionen die prinzipiell nicht berechenbar sind
(Man sagt auch: unentscheidbare Probleme).
...aber auch für die Praxis:
I
Wir können kein Programm bauen, das im Allgemeinen entscheiden kann,
ob sich ein gegebenes Programm “aufhängt”.
I
Dieses Problem erstreckt sich bereits auf Teile von Programmen:
1
2
3
4
5
Anweisung 1;
Anweisung 2;
...
Anweisung n;
print("hello");
Michael Grossniklaus · DBIS
• Die Anweisungen 1–n formen bereits ein
Teilprogramm.
• Im Allgemeinen ist also nicht entscheidbar, ob
die Anweisung in Zeile 5 jemals ausgeführt wird.
Informatik 2 · Sommer 2017
205
7 · Exkurs: Berechenbarkeit
Konsequenzen · 7.3
Bedeutung für Compiler
I
Ein Compiler hat auch die Aufgabe in unseren Programmen Fehler zu
finden, damit sie nicht erst im Betrieb auffallen.
• Dazu gehören “offensichtliche” Fehler wie z.B. falsche Syntax,
• weniger offensichtliche, wie nicht deklarierte Variablen oder Typfehler,
• und Fehler wie das “Abstürzen” (unerwarteter Zustand) oder “Aufhängen”
(nicht-Erreichen eines definierten Zustandes).
I
Dazu bieten sich zwei Strategien an:
1. Alles zurückweisen von dem der Compiler nicht beweisen kann, dass es
korrekt ist.
I
I
Das ist eine sehr starke Einschränkung. Solche Sprachen lassen viele legitime
Programme nicht mehr zu.
Der Simply Typed λ-Calculus28 ist ein Beispiel dafür. Dieser ist nicht
turingmächtig, dafür terminieren alle damit formulierten Programme.
2. Nur verbieten was der Compiler als definitiv falsch erkennt.
I
28 Eine
Hier wurde die Grenze der erkennbaren Fehler immer weiter verschoben.
Typsysteme sind eine Methode dazu.
Variante des λ-Kalküls, auf die wir nicht weiter eingehen werden.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
206
Herunterladen