Skript zu Abstrakter Interpretation

Werbung
Funktionale Programmierung: Analyse WS 2007/8, Abstrakte Interpretation, 24. Januar 2008
5.1
1
Abstrakte Interpretation, insbesondere zur
Striktheitsanalyse
Die Idee der Methode der abstrakten Interpretationen besteht darin, Eigenschaften von Programmen nachzuweisen, indem man statt der denotationalen Semantik eine approximative Semantik benutzt, d.h. eine Nicht-StandardInterpretation des Programms. Approximativ bedeutet hier nur, dass diese Interpretation nicht die volle exakte Rechnung durchführt. Abstrakte Interpretation kann im Prinzip für alle Programmiersprachen angewendet werden und ist
nicht auf funktionale Programmiersprachen beschränkt.
Diese Interpretation wird normalerweise abhängig von der zu zeigenden Eigenschaft gewählt. I.a. verwendet man Interpretationen in eine endliche Menge D,
die aber immer auch ein ⊥-Element für undefiniert“ enthältünd ein >-Element
”
für keine Information“ bzw. alles ist möglich“. Die Interpretation α ordnet den
”
”
Konstanten des Programms Werte in D zu. Der schwierigere Teil ist es, dann
den im Programm definierten Funktionen ebenfalls Funktionen über D zuzuordnen. Hierbei sollte im optimalen Fall eine Homomorphie-Eigenschaft gelten:
α(f a) = α(f ) α(a). Das kann nicht immer eingehalten werden, da die abstrakte Funktion α(f ) nicht wissen kann, was (f a) ist, und deswegen auch teilweise
einen weniger definierten Wert liefern wird. Wenn es eine Informationsordnung
≤ auf der Menge D gibt, die Elemente so ordnet, dass ⊥ ≤ d ≤ >, ist, dann
muss man aber erwarten, dass α(f a) ≤ α(f ) α(a) gilt.
Beispiel 5.1.1 Der 9er-Test ist eine abstrakte Interpretation. Das ist ein Test
(nach Adam Riese), ob eine von Hand ausgeführte Multiplikation von großen
ganzen Zahlen korrekt ist. Diese Methode beruht auf den Resten modulo 9. Der
9er Test war früher eine große Hilfe bei Multiplikationen die von Hand ausgeführt wurden. Allerdings gilt auch hier: nicht jeder Fehler kann damit entdeckt
werden.
Beispiel: 25 ∗ 25 = 625
Die Interpretation α berechnet für jede beteiligte Zahl: die Quersumme, dann
wieder die Quersumme der Quersumme, usw. bis man eine einstellige Zahl hat.
Das ergibt α(25) = 7 und α(625) = 4, da 625 → 13 → 4.
Die Multiplikation der abstrakten Werte wird genauso behandelt und ergibt:
α(α(25) ∗ α(25) = 4, da die Quersummenberechnung 7 ∗ 7 = 49 → 13 → 4
ergibt. Damit hat unsere Multiplikation den Test bestanden.
Wir sind nun etwas genauer: die endliche Menge D der abstrakten KonstantenWerte muss ein Bereich (engl.: domain) sein, d.h. es gibt darauf eine partielle
Ordnung ≤, so dass für alle Teilmengen sowohl eine (eindeutige) größte untere
Schranke (glb: greatest lower bound) und auch eine (eindeutige) kleinste untere
Schranke (lub: least upper bound) existiert. D.h. D, ≤ ist ein vollständiger Verband. Manchmal wird ein Verband auch mittels der Operationen glb, lub für 2
Elemente definiert. Die Menge {⊥, >} mit ⊥ < > erfüllt diese Bedingungen und
ist einfachste nichttriviale vollständige Verband. Es folgt auch, dass D immer
Funktionale Programmierung: Analyse WS 2007/8, Abstrakte Interpretation, 24. Januar 2008
2
ein ⊥ und > enthalten muss. Im allgemeinen interessiert man sich dafür, eine
möglichst gute abstrakte Interpretation von Funktionen zu finden.
Da D zunächst nur für die Konstanten definiert ist, muss man auch noch
vollständige Verbände zu den Funktionen vorsehen. Im allgemeinen enthält dann
ein solcher Bereich unendlich viele Elemente und trennt nicht zwischen Konstanten und Funktionen.
Etwas einfacher wird es, wenn man ein monomorphes Typsystem hat. Der zugehörige Funktions-Bereich ist dann eine Untermenge von Dn → D. Man kann
dann anhand der Definitionen der Funktionen die abstrakte interpretierte Funktion über dem endlichen Bereich effektiv berechnen. Diese Funktion approximiert das Abbildungsverhalten der betrachteten Funktion in der Standardinterpretation.
Wir beschränken uns auf folgenden auf eine möglichst vereinfachte Darstellung.
Wer hier tiefer einsteigen will, muss die Fachliteratur studieren.
Beispiel 5.1.2 Ein einfaches Beispiel zur Illustration sind die ganzen Zahlen
und Funktionen auf ganzen Zahlen.
Wir nehmen an, dass es eine Programmiersprache für Funktionen gibt, so dass
es Konstanten für alle ganzen Zahlen gibt. Als Funktionen betrachten wir die
Grundrechenarten: Addition, Multiplikation, Subtraktion, Division, < auf Zahlen, . . . .
Die Standard-Interpretation ist die Interpretation als Zahlen: Z⊥ . D.h, alle Zahlen als Konstanten, ein ⊥-Element, das kleiner als alle Zahlen ist, und ein >Element, das größer als alle Zahlen ist.
Als Beispiel für eine abstrakte Interpretation wollen wir die Abbildungseigenschaften von Funktionen bezüglich der positive/negativen Zahlen, d.h. bzgl. der
Vorzeichen der Zahlen berechnen, Z.B. − ∗ − = +.
Als Basisbereich der abstrakten Interpretation nehmen wir zunächst die Menge
{0, +, −, ⊥, >}. Die Elemente 0, +, − sind unvergleichbar, und ⊥ ≤ 0, +, − ≤ >.
Damit ist diese Menge ein vollständiger Verband.
Jetzt definieren wir zuerst mal die abstrakte Interpretation α der Konstanten:
α(n) = + , wenn n positiv
α(n) = −, wenn n negativ
α(n) = 0, wenn n = 0
α(⊥) = ⊥
Als abstrakte Interpretation von ∗ als Funktion α(∗) : D × D → D erhalten wir:
Funktionale Programmierung: Analyse WS 2007/8, Abstrakte Interpretation, 24. Januar 2008
α(∗) : +
0
0
+
−
0
0
+
usw.
+
+
−
−
−
⊥
>
>
→
→
→
→
→
→
→
→
3
+
0
0
−
+
⊥
0
>
Alle Werte lassen sich zufriedenstellend berechnen, und es gilt die
Homomorphie-Eigenschaft (ausnahmsweise) : Man verliert keine Information
bei der Multiplikation.
Der Versuch, die abstrakte Interpretation von + anzugeben, zeigt, dass es einen
Unterschied zur Standardinterpretation gibt, und dass man bei Berechnungen
Information verlieren kann und Approximation notwendig ist.
α(+) :
aber:
+ + → +
0 0 → 0
− − → −
> ⊥ → ⊥
+ − → ??
Das Ergebnis der letzten Zeile kann +, −, 0 sein, abhängig von den Argumenten.
Um diesen Fall möglichst einfach zu behandeln, und gleichzeitig die richtige
Approximation zu erhalten, müssen wir das Element > als Ergebnis benutzen.
Die abstrakte Interpretation von α(+) ergibt somit für die unklaren Fälle:
+ −
→
>
Wir sehen, dass α(a + b) ≤ α(a) α(+) α(b) ist, aber dass es Elemente gibt
mit α(a + b) < α(a) α(+) α(b), z.B. (−1 + 2) = 1, aber α(−1) α(+) α(2) =
− α(+) + = >.
Bemerkung 5.1.3 Man kann (z.B. im Zahlenbeispiel oben) andere abstrakte
Interpretationen folgendermaßen konstruieren: Man nimmt den Grundbereich
der Konstanten und wählt bestimmte TeilMengen von Z⊥ aus, die jeweils interessant sind. Diese Mengen müssen alle das Element ⊥ enthalten, und alle
Schnitte und Vereinigungen müssen ebenfalls als Mengen vorkommen. Dann besteht Dkonst für die Konstanten aus diesen Mengen (und auch der vollen Menge
Z⊥ , und die Ordnung ist die Teilmengenrelation.
Zum Beispiel die Eigenschaft
positiv“ entspricht dann der Menge
”
{1, 2, 3, . . . , } ∪ {⊥}.
Damit man für alle Funktionen die Abbildungseigenschaften berechnen kann, berechnet man die Funktionenräume darüber folgendermaßen:
Man berechnet für alle Funktionen und Mengen in Dkonst die Ergebnisse
bezüglich der Standardinterpretation und nimmt die lub’s der Ergebnisse:
Funktionale Programmierung: Analyse WS 2007/8, Abstrakte Interpretation, 24. Januar 2008
4
α(f )(M ) = lub{[[f ]](m) | m ∈ M }.
Dieser lub ist gerade die kleinste Menge, die in der Auswahl enthalten ist, und
die diese Menge noch enthält.
Wenn wir dies erreicht haben und α(.) berechnet haben, dann können wir etwas
über die Funktionen aussagen.
Diese Konstruktion ergibt für die positiv/negativ/0-Interpretation eine Menge
Dkonst = {{n | n > 0} ∪ {⊥}, {0, ⊥}, {n | n < 0} ∪ {⊥}, ⊥, Z ∪ {⊥}}
Diese kann man wieder mit {⊥, 0, +, −, >} bezeichnen.
Aus dieser Interpretation können wir dann Schlüsse über das Verhalten der
arithmetischen Funktion ziehen.
Praktisches Problem: Um mit dieser Methode eine abstrakte Interpretation
zu berechnen, benötigen wir zuerst die Standardinterpretation. Wenn wir
aber die Standardinterpretation kennen, dann wissen wir (fast) alles über die
Semantik der Funktion.
Normalerweise gilt:
1. Die praktische Berechnung einer abstrakten Interpretation erfolgt ohne
Berechnung der Standardinterpretation. I.a. sogar nur anhand des Programms zur Funktion. Bei abstrakter Interpretation zur Striktheitsanalyse benutzt auch ein Element ⊥P in den Programmen, dass eine bekannte
Nichtterminierung repräsentiert.
2. I.a. berechnet man nur eine Approximation einer solchen optimalen abstrakten Interpretation. Wir haben nur zu argumentieren, dass die Berechnungsmethode korrekt ist, d.h. eine Approximation liefert, aus der
wir noch die Eigenschaften schließen dürfen.
Beispiel 5.1.4 zminus x = x - x
Wir sehen: diese Funktion liefert 0 oder ⊥, d.h. wir könnten diese abstrakt
interpretieren als {> → 0, ⊥ → ⊥}.
Dies ist erlaubt als abstrakte Interpretation, aber normalerweise braucht man
ad-hoc Nachweise, die für jede Funktion anders aussehen. Die Berechnung des
besten Wertes bei beliebig definierten Funktionen ist unentscheidbar,. Wenn man
unendlich viele Werte durchgehen muß, um die abstrakte Interpretation zu berechnen, dann kann man das als Hinweis sehen, dass man in die Nähe eines solchen Unentscheidbarkeits-Problems kommt. Normalerweise berechnet man den
Wert modular, d.h. man setzt alle abstrakten Werte ein und bemüht sich, die
Unterfunktionen zu verwenden, deren Funktionswerte auf den abstrakten Werten möglicherweise schon bekannt sind. Wir erhalten:
zminus 0 = 0 − 0
=0
zminus + = + − + = > da man hier jeweils nicht weiß,
zminus − = −(−)− = > dass die Argumente gleich waren
Es gilt, dass diese Approximation auf jeden Fall größer ist (sein sollte) in der
Ordnung als die exakte.
Funktionale Programmierung: Analyse WS 2007/8, Abstrakte Interpretation, 24. Januar 2008
5.1.1
5
Formalisierung und Korrektheitsbedingung
Wir betrachten zunächst eine einfach getypte Sprache: PCF über num, bool.
Was wir hier davon nur brauchen, ist analog zu einem einfach getypten KFPT.
Da man vollständige Verbände benötigt, und das auch für Funktionen, z.B, von
Zahlen nach {⊥, T rue, F alse, >}, betrachten wir hier nur die Konstruktion für
Funktionen erster Ordnung.
Man startet mit dem Verband der ganzen Zahlen: Dnum
=
{⊥, 0, 1, 2, 3, . . . , −1, −2, −3 . . . , >} und dem entsprechenden Verband zu
den Booleschen Werten Dbool = {⊥, T rue, F alse, >}. Man braucht nun
Domains für Funktionen. Aber man darf nicht alle Funktionen auf den Mengen
nehmen, sondern nur die stetigen:
Das sind Funktionen, die (i) monoton sind, und die (ii) lub und
glb erhalten: d.h. f (lub(M )) = lub(f (M )). Bei endlichen, linear geordneten Bereichen reicht die Monotonie aus. Der Funktionenraum
[DBool → DBool ] enthält die Identität, die konstanten Funktionen,
und weitere monotone, stetige Funktionen. Kleinstes Element ist
λx.⊥, und größtes Element ist λx.>.
Wir werden jetzt nur angeben, wie man die abstrakte Interpretation ausrechnen
kann, und welche Bedingungen sinnvollerweise erfüllt sein müssen.
Funktionenräume sind wieder (endliche) Verbände:
• Top-element ist die Funktion, die alles auf > abbildet.
• Bot-element ist die Funktion, die alles auf ⊥ abbildet.
• Die Ordnung ist punktweise definiert, d.h. f ≤ g gdw. ∀x : f (x) ≤ g(x).
• ebenso sind lubs und glbs punktweise definiert.
Jedem monomorphen einfachen Typ τ können wir einen Verband zuordnen:
ϕ(num)
= Dnum
ϕ(bool)
= Dbool
D
=: Dnum ∪ Dbool
ϕ(τ1 → τ2 ) = [ϕ(τ1 ) → φ(τ2 )]
Für jedes Programmkonstrukt geben wir jetzt an, wie die abstrakte Interpretation berechnet werden kann. Der Fixpunktoperator von PCF ist µ.
Die Notation ist:
α(Programmfragment) Umgebung = Interpretation
wobei die Umgebung nur benötigt wird, wenn es im Programmfragment freie
Variablen gibt. Die Umgebung enthält dann Bindungen in D für die freien
Variablen.
Funktionale Programmierung: Analyse WS 2007/8, Abstrakte Interpretation, 24. Januar 2008
6
α(x) ρ
α(c) ρ
= ρ(x)
muß vorgegeben werden für Konstanten c
vom Typ num, bool ebenso für Konstruktoren
α(f g) ρ = (α(f ) ρ) (α(g) ρ)
α(λx.s) ρ = λy.(α(s) (ρ[y/x]))
α(µx.s) ρ = lub {(α(λx.s) ρ)i | i = 0, 1, 2, . . .}
Wobei f 0 = ⊥ und f i+1 = f (f i )
Für die Konstruktoren und andere Konstanten muß man deren abstrakte Interpretation vordefinieren. Allerdings hat man recht enge Schranken, wie eine
solche Interpretation beschaffen sein darf. Zum Teil kann man diese jedoch in
natürlicher Weise direkt aus den anderen Definitionen herleiten.
Für if und case muß man eine Definition vorgeben. Für if gibt es nicht so
viele Fälle: Wenn der abstrakte Bereich True und False enthält, dann ist
folgende Definition sinnvoll:
α(if e1 then e2 else e3 )ρ =
=
=
=
⊥,
wenn α(e1 ) ρ = ⊥
α(e2 ) ρ
wenn α(e1) ρ = True
α(e3 ) ρ
wenn α(e1) ρ = False
lub ((α(e2 ) ρ, α(e3 ) ρ) wenn α(e1 ) ρ = >
Im letzten Fall geht Information verloren, da man e2 und e3 gemeinsam approximiert. (Dieser Informationsverlust tritt bei der abstrakten Reduktion nicht!
ein). Analog kann man case-Ausdrücke behandeln.
Man kann auch für andere Funktionen, die durch Ausdrücke definiert sind, eine
abstrakte Interpretation bereits vorgeben. In diesem Fall ist die Korrektheit
ebenfalls noch zu zeigen.
Bedingungen für die Korrektheit der Striktheitsanalyse mittels abstrakter Interpretation
Eine wichtige Bedingung, die die abstrakte Interpretation für Anwendungen
erfüllen muss ist:
α(s t) ≤ α(s) α(t)
Diese erlaubt eine modulare Berechnung der abstrakten Interpretation.
Ähnliche Bedingungen müssen für die anderen Programmkonstrukte ebenfalls
gelten.
Vorsicht: Man kann von der abstrakten Interpretation nicht verlangen, dass
zB α(s) = ⊥ ⇔ s ∼c ⊥ gilt, da das bedeuten würde, dass die abstrakte Interpretation die Nichtterminierung aller Ausdrücke entscheiden könnte. Das kann
für abstrakte Interpretationen mit endlichem Bereich nicht richtig sein.
Man kann auch nicht verlangen, dass s ≤c t ⇒ α(s) ≤ α(t), denn das ergäbe
Funktionale Programmierung: Analyse WS 2007/8, Abstrakte Interpretation, 24. Januar 2008
7
dasselbe Dilemma. Ebenso ist α(s) ≤ α(t) ⇒ s ≤c t falsch, da es Elemente gibt,
über die man nichts weiß, d.h. α(s) = α(t) = >, aber die kontextuell verschieden
sind.
Wir konzentrieren uns hier auf Korrektheitsbedingungen für Striktheitsaussagen, die aus der abstrakten Interpretation folgen.
Wesentliche Bedingungen:
α bildet die Bot-Konstante des Programms auf das abstrakte ⊥ ab:
α (⊥P ) = ⊥
D.h. das konkrete ⊥P (die Konstante in Programmen) wird auf das
abstrakte ⊥ abgebildet.
Wenn diese Bedingung nicht erfüllt ist, dann verliert man unnötigerweise zuviel
Information!
Eine andere, schärfere Bedingung, die man für die Striktheitsanalyse benötigt,
ist die Bedingung Bot-Eindeutigkeit (bottom-reflecting):
Wenn α(d) = ⊥, dann ist [[d]] = ⊥, bzw. d ∼c ⊥
d.h. d ist undefiniert
Diese Bedingung ist notwendig, wenn man Striktheitsinformation folgern will.
Denn wenn sie nicht erfüllt ist, dann kann man aus α(d) = ⊥ nicht schließen,
dass der konkrete Wert d undefiniert ist.
Man kann nachweisen dass obige Bedingungen erfüllt sind, wenn man die Definition der abstrakten Interpretation der Programmkonstrukte richtig wählt. Z.B.
Konstanten möglich exakt, und bei den anderen Konstrukten möglichst knapp
nach oben abschätzt, wie z.B. beim if-then-else. Um
5.1.2
Berechnung von Striktheitsinformation in PCF
Definition 5.1.5 Eine Funktion f ist strikt im k-ten Argument, wenn für alle
Argumente ai gilt: f a1 . . . ak−1 ⊥ ak+1 . . . an = ⊥. D.h. wenn in der Standardinterpretation gilt:
[[ak ]] = ⊥ ⇒ [[f a1 . . . ak−1 ak ak+1 . . . an ]] = ⊥
Wir suchen eine abstrakte Interpretation, die Striktheitsinformation liefern
kann.
Der einfachste Basisbereich besteht nur aus {⊥, >}. Die abstrakte Interpretation
α soll für geschlossene e erfüllen:
α(e) = ⊥ gdw. [[e]] = ⊥ (bottom-reflecting)
Bzw. erweitert:
α(f > . . . > ⊥ > . . . >) = ⊥ ⇒ f ist strikt im k-ten Argument.
8
Funktionale Programmierung: Analyse WS 2007/8, Abstrakte Interpretation, 24. Januar 2008
Die passende abstrakte Interpretation kann man folgendermaßen definieren:
α(n)
= >
α(True)
= >
α(False)
= >
α(pred)
= {> → >, ⊥ → ⊥}
α(succ)
= {> → >, ⊥ → ⊥}
α(zero?)
= {> → >, ⊥ → ⊥}
D.h. diese Funktionen werden als Identität interpretiert.
DBeachte, dass diese in PCF strikt definiert sind.
α(s t)
= α(s)
α(t)
⊥
if α(e) ρ = ⊥
α(if e then e1 else e2 ) ρ =
lub(α(e1 ) ρ, α(e2 ) ρ) sonst
α(λx.e) ρ
= (λd.α(e)ρ[x 7→ d])
α(λx.e)
= f wobei f (α(d)ρ) := α(e[d/x]) falls e geschlossen
α(µx.e) ρ
= lub((α(λx.e)ρ)i )
Hierbei ist zu beachten, dass alle Funktionen als monotone Funktionen über dem
Bereich interpretiert werden und dass die Funktionsfolge, die bei der Berechnung
des Fixpunkts einer FUnktion berechnet wird, aufsteigend ist. D.h. f 0 ≤ f 1 ≤
f 2 ≤ . . ..
Dieser einfache Bereich kann zwischen True und False nicht unterscheiden, und
verliert somit die entsprechenden Informationen.
5.1.3
Beispiele
zur
Berechnung
(Striktheits-) Interpretation
der
abstrakten
Beispiel 5.1.6 Fixpunkt für Addition:
Beachte, dass die Funktionen zero?, succ, pred als Identität auf D interpretiert
werden.
add = µa, λs, t.if (zero? s) then t else succ (a (pred s) t)
Wir nehmen den Bereich {⊥, >}.
Im folgenden meinen wir mit (s seq t) die Funktion bzgl des Domains {⊥, >},
die zuerst s auswertet; falls ⊥, wird ⊥ als Wert zurückgegeben, wenn >, dann
t auswertet, und der Wert ist der Wert von t. Damit kann man die abstrakte
Interpretation für if-then-else auch so schreiben:
α(if e then e1 else e2 ) ρ =
(α(e) ρ) seq lub(α(e1 )ρ, α(e2 )ρ)
D.h. die abstrakte Funktion
⊥
⊥
>
>
⊥
>
⊥
>
→⊥
→⊥
→⊥
→>
Funktionale Programmierung: Analyse WS 2007/8, Abstrakte Interpretation, 24. Januar 2008
9
Fixpunktiteration für add:
1) i = 0 :
2) i = 1 :
3) i = 2 :
a0 = ⊥
a1 = λs, t.s seq t
a2 = λs, t.s seq lub(t, succ (pred (s) seq t)
= λs, t.s seq lub(t, s seq t)
= λs, t.s seq lub(t, t)
= λs, t.s seq t
D.h. Fixpunkt ist gefunden: add ist strikt in beiden Argumenten.
Beispiel 5.1.7
tak x y z = if x <= y
tak (tak
(tak
(tak
then z else
(x-1) y z)
(y-1) z x)
(z-1) x y))
Beh: tak ist strikt in allen Argumenten
Iteration:
1. tak0 = ⊥
tak1 x y z = λx, y, z.x seq y seq z
2. tak1 = λx, y, z.x seq y seq z
tak2 x y z = λx, y, z.x seq y seq z (nach einigem Rechnen und Ausnutzen
der Striktheit von −).
Vergleich
Das Vorgehen bei abstrakter Interpretation ist nicht so sehr verschieden von
dem der Abstrakten Reduktion.
Unterschiede ergeben sich z.B.
• Die Abstrakte Interpretations-Methode kann berechnete Information speichern und ausnutzen. Abstrakte Reduktion kann das im Prinzip auch, aber
es gibt ja nur die Striktheitsinformation.
• Komplexität: Abstrakte Interpretation ist hyper-exponentiell bei Funktionen höherer Ordnung (als untere Schranke, da alles berechnet werden
muß, denn sonst kann man die Fixpunkteigenschaft nicht feststellen. Die
Größe der Funktionsdomains wächst stark.
Abstrakte Reduktion: Es wird nur benötigte Information berechnet, man
kann approximiert und bei bestimmten Schranken abbrechen kann.
Abstrakte Reduktion:hat besseres best-case-Verhalten.
• Abstrakte Interpretation kann keine Kontextinformation erkennen, d.h.
man muß für den vorbestimmten Satz von Funktionen jeweils die abstrakte Interpretation ausrechnen. Man kann nicht automatisch etwas feiner
berechnen, als es der Domain zuläßt.
Funktionale Programmierung: Analyse WS 2007/8, Abstrakte Interpretation, 24. Januar 2008
10
Abstrakte Reduktion: kann Kontextinformation verwenden. Hierbei werden evtl Berechnungen mehrfach durchgeführt.
• Abstrakte Interpretation hat festgelegtes Approximationsverhalten und
kann evtl. einen Fixpunkt berechnen, während die Abstrakte Reduktion
in eine Schleife läuft.
Allerdings kann Abstrakte Reduktion manchmal den Wert berechnen,
während Abstrakte Interpretation überapproximiert.
• Abstrakte Interpretation kann nicht gut mit Konstruktoren umgehen, da
man jedesmal den Domain neu konstruieren muss. Zudem funktioniert
abstrakte Interpretation nicht auf unendlich vielen Werten. Wadler’s 4Punkt Bereich unten beschreibt eine nichttriviale Listeninterpretation.
• Abstrakte Interpretation kann so gut wie nicht mit Nichtdeterminismus
umgehen, da man dafür erst mal einen Domain angeben muss, während
abstrakte Reduktion hier anpassungsfähiger ist.
11
Funktionale Programmierung: Analyse WS 2007/8, Abstrakte Interpretation, 24. Januar 2008
5.1.4
4-Punkt Bereich für Listen (nach Phil Wadler)
Man nimmt folgende Domain {⊥, Inf, BotElem, >} zur Interpretation:
⊥ ≤ Inf ≤ BotElem ≤ >.
⊥
= undefiniert; WHNF-Auswertung terminiert nicht
Inf
= unendliche Listen (Tail ist niemals Nil); SpineAuswertung terminiert nicht
BotElem = Listen, in denen mindestens ein Element = ⊥ ist,
oder deren Tail niemals Nil ist; Auswertung aller
Listenelemente terminiert nicht.
>
= alle Listen
Wir erinnern uns an die Definition der Demands:
Inf
= h⊥, > : Infi
BotElem = h⊥, ⊥ : >, > : BotElemi
Das hilft der Intuition, aber man muss für die damit definierte abstrakte Interpretation dann nachweisen, dass sie das Erwartete leistet.
Interpretation von caselst ist unten definiert. Die Fallunterscheidung ist dabei
wie eine Funktion behandelt: Z.B.in (y : ys) → e2 wird e2 wie die Funktion
λy, ys.e2 behandelt.
α(caselst
=
=
=
=
e of Nil → e1 ; y : ys → e2 ) ρ
⊥,
wenn
(α(e2 ) ρ)> Inf
wenn
lub{(α(e2 ) ρ) ⊥ >, (α(e2 ) ρ) > BotElem} wenn
lub{(α(e1 ) ρ), (α(e2 ) ρ) > >}
wenn
α(e)
α(e)
α(e)
α(e)
ρ=⊥
ρ = Inf
ρ = BotElem
ρ=>
Man muss auch die abstrakte Interpretation des Nil und (:)-Konstruktors
angeben:
α(Nil)
α(:)
x, ⊥
x, Inf
x, BotElem
⊥, >
Inf, >
BotElem, >
>, >
=
=
=
=
=
=
=
=
>
Inf
x∈D
Inf
x∈D
BotElem x ∈ D
BotElem
>
>
>
Beispiel 5.1.8 length xs = case_lst xs of
Nil -> 0;
y:ys -> 1 + (length ys)
Iterationen
Funktionale Programmierung: Analyse WS 2007/8, Abstrakte Interpretation, 24. Januar 2008
1. length0
2. length1
3. length2
4. length3
12
= ⊥
= {⊥ → ⊥, Inf → ⊥, BotElem → ⊥, > → >}
= {⊥ → ⊥, Inf → ⊥, BotElem → >, > → >}
Da der rekursive Aufruf > + (length1 >) auftritt.
= {⊥ → ⊥, Inf → ⊥, BotElem → >, > → >}
Es ändert sich nichts mehr.
Wenn wir die Korrektheit mal annehmen, ebenso die Eigenschaft der BotEindeutigkeit, dann haben wir mit dieser Methode gezeigt, dass
length Inf = ⊥
d.h. Die Längenfunktion terminiert nicht für unendliche Listen.
Beispiel 5.1.9 Man kann auch für die map-Funktion nachweisen, dass diese
insbesondere die Abbildungseigenschaft Inf → Inf hat.
map f xs = case_lst xs of
Nil -> Nil;
y:ys -> f y : map f ys
Iterationen
1. (map f )0
2. (map f )1
3. (map f )2
4. (map f )3
= ⊥
= {⊥ → ⊥, Inf → Inf, BotElem → Inf, > → >}
Da (f >) : ⊥ = Inf
= {⊥ → ⊥, Inf → Inf, BotElem → >, > → >}
Da > : Inf = Inf.
= (map f )2
Beachte, dass (map f )0 ≤ (map f )1 ≤ (map f )2 ≤ . . ..
Das kann man so interpretieren, dass map Listen die kein Nil am Ende haben
wieder in solche überführt. Man kann daraus nicht schließen , dass unendliche
Listen wieder in unendliche überführt werden, da auch zB Nil : ⊥ zu Infgehört,
aber keine unendliche Liste im eigentlichen Sinne ist.
Herunterladen