Hilbert-Stil

Werbung
Typsysteme Typisierung Polymorphie Typinferenz
Einführung in die Funktionale
Programmierung mit Haskell
Typsysteme & Typinferenz
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
12. Juni 2013
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Planung
Klausur: 19.7. 10h
Vorläufige Planung:
Mi Vorlesung
Do Vorlesung
17. Apr 13
18. Apr 13
24. Apr 13
25. Apr 13
1. Mai 13
2. Mai 13
8. Mai 13
9. Mai 13
15. Mai 13
16. Mai 13
22. Mai 13
23. Mai 13
29. Mai 13
30. Mai 13
5. Jun 13
6. Jun 13
12. Jun 13
13. Jun 13
19. Jun 13
20. Jun 13
26. Jun 13
27. Jun 13
3. Jul 13
4. Jul 13
10. Jul 13
11. Jul 13
17. Jul 13
18. Jul 13
Fr Übung
19. Apr 13
26. Apr 13
3. Mai 13
10. Mai 13
17. Mai 13
24. Mai 13
31. Mai 13
7. Jun 13
14. Jun 13
21. Jun 13
28. Jun 13
5. Jul 13
12. Jul 13
19. Jul 13
Steffen Jost
Sollumfang:
Raumbuchung:
3+2
4+2
42 Termine, davon
3 Feiertage
4 Übung nachholen
6 Entfällt
1 Klausurtermin
= 20V + 12Ü
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Typprüfung
Wir haben bisher behauptet:
Das statische Typsystem von Haskell prüft während dem
Kompilieren alle Typen:
Keine Typfehler und keine Typprüfung zur Laufzeit
Kompiler findet bereits viele Programmierfehler
Kompiler kann besser optimieren
“Well-typed programs can’t go wrong!” R. Milner 1934-2010
Heute klären wir:
Was genau sind Typfehler?
Was ist der Unterschied zwischen getypten und ungetypten
Sprachen?
Wie bestimmt man den Typ eines Programmes?
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Typfehler
Offensichtlich machen folgende Ausdrücke keinen Sinn:
"Hello" * True
0.23 + ’a’
False && (\x -> "error")
(*) 1 (+) 2 3 4
(\f -> f 42) 69
if 3 * x then 42 else "nothing"
Ist dies wirklich offensichtlich?
Wie formalisieren wir die Intuition, welche Ausdrücke korrekt sind?
Wie ist das in anderen Sprachen?
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Typsysteme in der Praxis
In der Praxis gibt es durchaus unterschiedliche Ansätze zum
Einsatz von Typsystemen in der Programmierung.
Was passiert bei der Auswertung von (\f -> f 42) 69?
Keine Typprüfung: 69 wird als Sprungaddresse interpretiert.
Systemabsturz oder schwere Betriebssystem-Ausnahme möglich.
Dynamische Typprüfung: Werte tragen zur Laufzeit
Typ-Markierung Ganzzahl, Funktion, . . .
Da 69 keine Funktion ist, wird eine Ausnahme im
Laufzeitsystem geworfen.
Statische Typprüfung: Der Übersetzer weigert sich, ein
Programm mit Typfehlern zu übersetzen. Es kommt nie zu
derartigen Laufzeitfehlern.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Typsysteme in der Praxis
Unterschiedliche Ansätze zum Einsatz von Typsystemen:
Keine Typprüfung:
Assembler
⊕ Schnelle Ausführung; Viel Freiheit für Programmierer
Keinerlei Sicherheit; Schwere Fehler möglich
Dynamische Typprüfung:
LISP, Scheme, BASIC, Skriptsprachen: JavaScript, Python
⊕ Viel Freiheit für Programmierer
Ständige Typrüfung zur Laufzeit verlangsamt Ausführung
Statische Typprüfung:
Haskell, SML, Ocaml, Scala, Eingeschränkt: C, Java
⊕ Kompiler schliesst viele Fehler von vorn herein aus
Eingeschränkte Freiheit beim Programmieren; Langwierige
Fehlermeldung nerven beim Kompilieren
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Programmausdrücke
Zur Vereinfachung betrachten wir nur ein Haskell-Fragment.
Der λ-Kalkül (1936, Alonzo Church(1902-95)) beschreibt den
funktionalen Kern von Haskell.
Ein Programmausdruck e im Lambda-Kalkül ist:
e ::= x
Variable
foo
| c
Konstante
2.718
| e1 e2 Anwendung
sqrt 5.0
| λx.e Funktionsabstraktion (\x -> e)
(e wie expression, engl. für “Ausdruck”)
Der Einfachheit verwenden wir die Haskell-Syntax
Weitere Haskell-Ausdrücke werden nach Bedarf
hinzugenommen
Infix-Operatoren schreiben wir hier meist in präfix Notation,
also (+) 2 7 anstatt 2 + 7
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typausdrücke
Ein Typausdruck A is definiert durch:
A, B ::= α | β | . . .
| Int | Double | String | . . .
| A→B
Typvariable
a, b
Basistyp
Funktionstyp Int -> Bool
Wir beschränken uns auf Grund- und Funktionstypen;
also keine Listen, keine Typklassen, etc.
Funktionstypen sind weiterhin implizit rechts-geklammert:
A → B → C = A → (B → C ) 6= (A → B) → C
Eventuell verwenden wir Abkürzungen für Basistypen
I, D, S, . . . anstatt Int, Double, String, . . . .
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typzuweisung
Eine Typzuweisung e::A ist ein Paar, bestehend aus einem
Programmausdruck e und einen Typausdruck A,
interpretiert als Aussage “e hat Typ A”
4
:: Int
wahr
true
:: String
falsch
3.3
:: a
falsch
\x -> x - 1 :: Int-> Int
wahr
\x y -> x
:: a -> b -> a wahr
\x y -> y
:: a -> b -> a falsch
z (x + 0)
:: Bool
???
Typzuweisungs-Aussagen können wahr oder falsch sein
In der Literatur wird oft e::A anstatt e::A geschrieben
Der Typausdruck z (x + 0) enthält unbekannte, freie
Variablen x und z, deren Typ wir nicht kennen
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typkontext
Ein Typ(isierungs)kontext Γ ist eine endliche Menge von
Typzuweisungen
Γ = {x1 ::A1 , . . . xn ::An }
(n ≥ 0)
wobei die Typvariablen xi paarweise verschieden sein müssen.
(engl. typing context/environment)
{x::Bool, y::Int → Int}
{z::Int → Bool, x::Int}
{h::Int → Bool, h::Int}
gültig
gültig
ungültig
Γ ist endliche Abbildung von Variablen x auf Typen Γ(x)
Die Reihenfolge der Typzuweisungen in Γ ist unbedeutend
Wir schreiben Γ, x::A um x::A in Γ einzufügen;
falls x ∈ dom(Γ) dann ist Γ, x::A undefiniert
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typisierung
Ein Typurteil ist eine Aussage der Form: “Unter der Annahme,
dass Variablen die in Γ angegebenen Typen haben, hat Ausdruck e
den Typ A”
(engl. typing judgement)
Wir schreiben kurz
Γ ` e::A
für “Ausdruck e den Typ A in Kontext Γ”.
Formal ist ` : eine dreistellige Relation zwischen
Typkontexten, Ausdrücken, und Typen.
Beispiele:
{x::Int}
` \y → (+) x y
: Int → Int
gültig
{x::Bool} ` \y → (+) x y
: Int → Int
ungültig
{y : α}
: β→α
gültig
` \x → y
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typregeln
Ein Typurteil ist nur dann gültig, wenn es durch Typregeln
hergeleitet werden kann. Typregeln haben immer die Form:
P1 . . . Pn
K
(Name der Typregel)
Dabei sind P1 . . . Pn die Prämissen der Typregel und K die
Konklusion. Sind alle Prämissen gültig, dann auch die Konklusion.
Pi und K sind in der Regel Typurteile
Typregeln ohne Prämissen heißen Axiome
Z.B. für jede Konstante c gibt es ein Typaxiom.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typregel Var
Der Typ einer Variablen ist durch den Kontext gegeben:
(x::A) ∈ Γ
Γ ` x::A
(Var)
Da Γ als endliche Abbildung aufgefasst werden kann, können wir
diese Typregel auch äquivalent kurz schreiben durch:
Γ ` x::Γ(x)
(Var)
Beispiele:
{x::Double}
{x::Double, y ::Int, z::Bool}
{x::Double, y ::Int, z::Bool}
{}
Steffen Jost
`
`
`
`
x::Double
y ::Int
y ::Double
x::Double
gültig
gültig
falscher Typ
ungebundene Variable
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typregel Const
Für jede Konstante eines jeden Basistypen definieren wir ein
Axiom (Regel ohne Prämisse), welches die Konstante erkennt:
(True)
(False)
Γ ` True::Bool
Γ ` False::Bool
Dabei ist der Kontext Γ hier beliebig, d.h. wir definieren ein
Axiomenschema für jedes Γ.
Mehrere Konstanten fassen wir mit folgenden Schemen zusammen:
c ist eine Integer Konstante
c ist eine Double Konstante
Γ ` c::Int
Γ ` c::Double
(Int)
(Double)
Es ist sowohl {} ` 5::Int als auch {} ` 5::Double herleitbar.
Dies ist unproblematisch so lange wir Typklassen ignorieren.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typregel Funktionsanwendung
Anwendung einer Funktion e1 mit Typ A → B auf
Argument e2 von Typ A liefert ein Ergebnis des Typs B:
Γ ` e1 ::A → B
Γ ` e2 ::A
Γ ` e1 e2 ::B
(App)
Diese Regel ist nicht anwendbar, falls:
e1 nicht von einem Funktionstypen ist
e2 nicht von Typ A ist, d.h. nicht im Definitionsbereich liegt.
Beispiel:
{x::Bool → Bool, y ::Bool} ` x::Bool → Bool
{x::Bool → Bool, y ::Bool} ` y ::Bool
{x::Bool → Bool, y ::Bool} ` xy ::Bool
(App)
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Beispiel Funktionsanwendung
Γ ` e1 :: A → B
Γ ` e2 :: A
Γ ` e1 e2 :: B
(App)
Zur Anwendung der allgemeinen Typregel werden Metavariablen
gemäß Kontext, Ausdruck und Typ instanziiert:
Γ ` \z → (+) z x::Int → Int
Γ ` (∗) 2 y ::Int
(App)
Γ ` \z → (+) z x (∗) 2 y :: Int
. . . ist z.B. herleitbar für Γ = {x::Int, y :: Int}.
Alternative Schreibweise in Infix-Notation:
Γ ` \z → z + x :: Int → Int
Γ ` 2 ∗ y :: Int
(App)
Γ ` \z → z + x 2 ∗ y :: Int
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Beispiel Funktionsanwendung
Γ ` e1 :: A → B
Γ ` e2 :: A
Γ ` e1 e2 :: B
(App)
Zur Anwendung der allgemeinen Typregel werden Metavariablen
gemäß Kontext, Ausdruck und Typ instanziiert:
Γ ` \z → (+) z x::Int → Int
Γ ` (∗) 2 y ::Int
(App)
Γ ` \z → (+) z x (∗) 2 y :: Int
. . . ist z.B. herleitbar für Γ = {x::Int, y :: Int}.
Alternative Schreibweise in Infix-Notation:
Γ ` \z → z + x :: Int → Int
Γ ` 2 ∗ y :: Int
(App)
Γ ` \z → z + x 2 ∗ y :: Int
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typregel Funktionsabstraktion
Die Typregel für anonyme Funktionsabstraktion lautet:
Γ, x::A ` e::B
Γ ` \x → e :: A → B
(Abs)
“Hat der Programmausdruck e den Typ B unter der Annahme
x : A, so hat der Programmausdruck \x → e den Typ A → B”
Man nennt e den Funktionsrumpf der Funktion \x → e, im
Funktionsrumpf kommt die abstrahierte Variable x frei vor, in dem
Programmausdruck \x → e gebunden.
Man sagt auch “Lambda \ bindet die darauf folgende Variable”
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typherleitung
Eine Typherleitung / Typbeweis ist ein endlicher Baum, wobei
alle die Blätter Typaxiome sind;
alle Knoten derart Instanzen von Typregeln sind, dass
die Beschriftung des Knotens die Konklusion ist,
und die Kinder des Knotens die Prämissen sind.
Die Beschriftung des Wurzelknotens ist die hergeleitete Aussage.
Eine Typaussage ist herleitbar, wenn sie eine Herleitung hat.
Eine Typherleitung ist letztendlich nur eine verkette Anwendung
von Typregeln.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Beispiel Herleitungsbaum
Eine Herleitung als Baum dargestellt:
{x::α, f ::α → β} ` f :: α → β
Var
{x::α, f ::α → β} ` x :: α
App
{x::α, f ::α → β} ` f x :: β
{x::α} ` \f → f x :: (α → β) → β
Var
Abs
{} ` \x → (\f → f x) :: α → (α → β) → β
Abs
Den Namen der angewendeten Regel schreiben wir an den rechten
Rand, damit man besser nachvollziehen kann, was gemacht wurde.
In Haskell könnten wir anstatt \x → (\f → f x) auch einfach
\x f → f x schreiben, doch unsere Typregel Abs haben wir der
Einfachheit halber nur für ein Argument definiert – was ja keinen
Unterschied macht!
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Beispiel Herleitungen
Eine Herleitung in linearer Schreibweise:
1
2
3
4
5
{x::α, f ::α → β} ` f ::α → β
{x::α, f ::α → β} ` x::α
{x::α, f ::α → β} ` f x::β
{x::α} ` \f → f x::(α → β) → β
{} ` \x → (\f → f x)::α → (α → β) → β
Var
Var
App(1, 2)
Abs(3)
Abs(4)
Bei dieser Schreibweise müssen wir alle Zeilen nummerieren.
Bei jeder Regelanwendung listen wir dann die verwendeten
Prämissen der Reihe nach auf.
Erstellt werden Herleitungen normalerweise von unten nach
oben, gelesen werden sie aber meist von oben nach unten.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Kontexterweiterung
Eine Typaussage bleibt gültig, wenn wir den Typisierungkontext
erweitern. Bsp:
{x::α}
` \f → f x :: (α → β) → β
{x::α, y ::γ}
` \f → f x :: (α → β) → β
{f ::Int → Bool, x::α, y ::γ} ` \f → f x :: (α → β) → β
Lemma (Kontexterweiterung/Abschwächung)
Wenn Γ ⊆ Γ0 und Γ ` e : A herleitbar ist, dann auch Γ0 ` e : A.
Beweisbar durch Induktion über die Länge der Herleitung.
Daraus leiten wir folgende strukturelle Typregel ab:
Γ0 ⊇ Γ
Γ0
Γ`e:A
`e:A
(Weak)
Die Regel ist jederzeit anwendbar, da sie unabhängig von e ist.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Einführung in die Funktionale
Programmierung mit Haskell
Typsysteme & Typinferenz
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
19. Juni 2013
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typregeln
Wdh.
Ein Typurteil ist nur dann gültig, wenn es durch Typregeln
hergeleitet werden kann. Typregeln haben immer die Form:
P1 . . . Pn
K
(Name der Typregel)
Dabei sind P1 . . . Pn die Prämissen der Typregel und K die
Konklusion. Sind alle Prämissen gültig, dann auch die Konklusion.
Pi und K sind in der Regel Typurteile
Typregeln ohne Prämissen heißen Axiome
Z.B. für jede Konstante c gibt es ein Typaxiom.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Bisher behandelte Typregeln
Wdh
Folgende Typregeln wurden bisher in der Vorlesung behandelt:
Γ ` x :: Γ(x)
c ist eine Integer Konstante
Γ ` c :: Int
Γ ` e1 :: A → B
Γ ` e2 :: A
Γ ` e1 e2 :: B
Γ, x::A ` e :: B
Γ ` \x → e :: A → B
Steffen Jost
(Var)
(Int)
(App)
(Abs)
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typherleitung Beispiel
An der Tafel konstruieren wir eine Typherleitung für das Typurteil:
(+)::Int → Int → Int, z::α ` (+) 4 (\x -> 1) z :: Int
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Weakening
Wdh
Wir hatten gesehen, dass folgende Typregel zulässig ist, da
Kontexterweiterungen harmlos sind:
Γ0 ⊇ Γ
Γ0
Γ`e:A
`e:A
(Weak)
Intuitiv: “Es schadet nie, mehr zu wissen als nötig”
Eine Typregel ist zulässig (engl. admissible), wenn deren
Hinzunahme keine neuen Herleitungen ermöglicht.
D.h. die Regel kann Herleitungen lediglich vereinfachen.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Exchange
Da wir Typkontexte als Mengen von Typzuweisungen definiert
haben, ist die Reihenfolge der Typannahmen bedeutungslos.
Dies können wir ebenfalls durch eine zulässige Regel ausdrücken:
Γ, x::A, y ::B, ∆ ` e :: C
Γ, y ::B, x::A, ∆ ` e :: C
Steffen Jost
(Exchange)
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Kontraktion
Mehrfachverwendung von Variablen ist harmlos:
Γ, x::A, y ::A ` e :: C
Γ, z::A ` e[z/x, z/y ] :: C
(Contraction)
Termsubstitution:
e[z/x, z/y ] bezeichnet den Term e, bei den alle freie Vorkommen
von x und y ersetzt werden.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Termsubstitution:
Wir definieren Substitution für Terme wir folgt:
x[e0 /x] := e0
y [e0 /x] := y
(e1 e2 )[e0 /x] := e1 [e0 /x] e2 [e0 /x]
(\y -> e1 )[e0 /x] := \x -> e1 [e0 /x] falls y ∈
/ FV(e0 )
(\x -> e1 )[e0 /x]:= \x -> e0
(\y -> e1 )[e0 /x]:= \y 0 -> (e1 [y 0 /y ])[e0 /x] für y 0 eine frische Variable
Bei Substitutionen muss man aufpassen, dass freie Variablen nicht
versehentlich eingefangen werden:
Wir schreiben kurz e0 [e1 /x, e2 /y ] für e0 [e1 /x] [e2 /y ], d.h.
mehrere Substitution arbeiten wir von links nach rechts ab.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Strukturelle Typregeln
Üblicherweise gelten diese drei strukturellen Typregeln implizit,
wenn nichts anderes angegeben wurde.
Zusätzliche Annahmen sind harmlos:
Γ0 ⊇ Γ
Γ0
Γ`e:A
(Weak)
`e:A
Reihenfolge der Annahmen ist bedeutungslos:
Γ, x::A, y ::B, ∆ ` e :: C
Γ, y ::B, x::A, ∆ ` e :: C
(Exchange)
Eine Variable kann mehrfach verwendet werden:
Γ, x::A, y ::A ` e :: C
z∈
/ FV(e)
Γ, z::A ` e[z/x, z/y ] :: C
Steffen Jost
(Contraction)
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Bisher behandelte Typregeln II
Übung 7 behandelte Typregeln für Bildung von Paaren und deren
Elimination durch Pattern-Matching so wie if-then-else Ausdrücke:
Γ ` e1 :: A
Γ ` e2 :: B
Γ ` (e1 , e2 ) :: (A, B)
Γ ` e1 :: (B, C )
Γ, x::B, y ::C ` e2 :: A
Γ ` let (x, y ) = e1 in e2 :: A
Γ ` e1 : Bool
(Pair-Intro)
Γ ` e2 : A
(Pair-Elim)
Γ ` e3 : A
Γ ` if e1 then e2 else e3 : A
(Cond)
Dabei wurden neue Programm- und Typ-ausdrücke eingeführt.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typregeln Paare
Neue Programmausdrücke:
(e1 , e2 )
let (x, y ) = e1 in e2
Paar-Introduktion
Paar-Elimination
Neuer Typausdruck: (A, B)
Neue Typregeln:
Γ ` e1 :: A
Γ ` e2 :: B
Γ ` (e1 , e2 ) :: (A, B)
Γ ` e1 :: (B, C )
(Pair-Intro)
Γ, x::B, y ::C ` e2 :: A
Γ ` let (x, y ) = e1 in e2 :: A
(Pair-Elim)
Bedeutung:
Typ eines Paares ist das Produkt der Typen der Einzelteile
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Curry-Howard-Isomorphismus
Beobachtung Regeln treten of in Paaren auf: zur Einführung
eines Typs und zu Elimination eines Typs
Abs/App, Pair-Intro/Pair-Elim
Dies ist sehr ähnlich zur mathematischen Beweisführung im
Hilbert-Stil, welche ebenfalls Herleitungsbäume verwendet.
Curry-Howard-Isomorphismus
Ein Typ kann als logische Formel aufgefasst werden.
Gibt es einen Programmausdruck zu einem Typ, so ist die
entsprechende logische Formel intuitionistisch beweisbar.
Typ A → B entspricht der logischen Implikation,
Typ (A, B) entspricht dem logischen “und”, usw.
Beobachtet durch Curry & Feys (1958) und Howard (1969)
Anwendung Übertragung von Erkenntnissen zwischen
Fachgebieten, Konstruktion von Beweisassistenten, Automatische
Programmextraktion aus intuitionistischen Beweisen
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Typregel Konditional
Neuer Programmausdruck: if e1 then e2 else e3
Neue Typregel:
Γ ` e1 :: Bool
Γ ` e2 :: A
Γ ` e3 :: A
Γ ` if e1 then e2 else e3 :: A
(Cond)
Bedeutung:
Bedingung muss Typ Bool haben
Zweige müssen vom gleichen Typ wie Gesamtausdruck sein
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Safety = Progress + Preservation
Eine Typherleitung ist ein formaler Beweis, dass ein Typausdruck
einen bestimmten Typ hat.
Was nutzt das?
Wir behaupteten: “Well-typed programs can’t go wrong”
Dies beweist man üblicherweise in 2 Schritten:
Progress
Jeder wohl-typisierte Programmausdruck ist entweder ein Wert
oder er kann weiter ausgewertet werden.
Preservation
Auswertung verändert den Typ eines Programmausdrucks nicht.
Für diese Beweise müssten wir Substitutionsmodell durch
Auswerteregeln formalisieren, wie z.B.
Operationale Semantik
e1 [e2 /x] ; v
(;-App)
(\x -> e1 ) e2 ; v
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Grundlagen Typregeln Typherleitung Strukturell Ausblick
Beweisidee:
Solche Beweise werden üblicherweise mit Induktion über die Länge
der Typherleitung geführt:
Um zu zeigen, dass if e1 then e2 else e3 ausgewertet werden
kann, darf man annehmen dann, dass e1 zu einem Wert
ausgewertet werden kann, da die Typherleitung für Γ ` e1 : Bool ja
ein Schritt kleiner sein muss; usw.
e1 ; True
e2 ; v
if e1 then e2 else e3 ; v
Γ ` e1 :: Bool
Γ ` e2 :: A
Γ ` e3 :: A
Γ ` if e1 then e2 else e3 :: A
(;-If-T)
(Cond)
Auswerteregeln und Typregeln müssen übereinstimmen.
Auswerteregeln und Implementation müssen übereinstimmen.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Typsubstitutionen Prinzipale Typisierung Inferenzprobleme
Polymorphie
Wie wir wissen, können Ausdrücke mehr als einen Typ haben,
z.B. für den Programmausdruck f x sind herleitbar:
{x::Int, f ::Int → Int}
{x::Bool, f ::Bool → Int}
{x::α, f ::α → α}
{x::α, f ::α → β}
`
`
`
`
f
f
f
f
x
x
x
x
::
::
::
::
Int
Int
α
β
Letzte hier die allgemeinste Typisierung (engl. principal typing):
jede andere Typisierung von f x erhält man daraus durch Einsetzen
von Typen für die Typvariablen α und β (Instanziierung).
{x::α, f ::α → β}[Bool/α, Int/β] ` f x :: β[Bool/α, Int/β]
Man kann beweisen: Allgemeinste Typisierung eines Ausdrucks ist
bis auf Umbenennung von Typvariablen eindeutig.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Typsubstitutionen Prinzipale Typisierung Inferenzprobleme
Typsubstitutionen
Eine Typsubstitution σ ist eine endliche Abbildung von
Typvariablen α auf Typausdrücke A.
σ1 =
=
=
σ2 =
σ3 =
σ4 =
[Bool/α, Int/β]
[Bool/α, Int/β, γ/γ]
[Bool/α, Int/β, γ/γ, δ/δ] . . .
[β/α, (β → Bool)/β, Int/γ]
[γ/α, δ/β]
[β/α, α/β]
Für die Anwendung Aσ einer Substitution σ auf Typen A gilt z.B.:
Int σ
= Int
(A → B)σ
= Aσ → Bσ
α[Bool/α, Int/β] = Bool
Da bei uns alle Typvariablen frei sind, gibt es keine
vergleichbaren Probleme wir bei der Termsubstitution.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Typsubstitutionen Prinzipale Typisierung Inferenzprobleme
Instanziierung
Wir definieren die Anwendung einer einer Substitution σ auf einen
Typkontext Γ punktweise:
(Γσ)(x) := (Γ(x))σ
Beispiel
{x::Int, y ::α → β, z::α}[γ/α, Int/β, Bool/γ] =
{x::Int, y ::Bool → Int, z::Bool}
Es ist beweisbar, dass Typurteile unter Substitution gültig bleiben:
Lemma (Typerhaltung unter Substitution)
Wenn Γ ` e :: A, dann Γσ ` e :: Aσ.
Wir leiten daraus erneut eine zulässige Typregel ab:
Γ ` e :: A
Γσ ` e :: Aσ
Steffen Jost
(subst)
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Typsubstitutionen Prinzipale Typisierung Inferenzprobleme
Substitutionskomposition
Die Komposition σσ 0 zweier Substitutionen definieren wir wieder
von links nach rechts:
A(σ1 σ2 ) := (Aσ1 )σ2
Eine Komposition können wir oft vereinfachen:
α[(α → β)/α][Int/α, Bool/β] = (α → β)[Int/α, Bool/β]
= Int → Bool
β[(α → β)/α][Int/α, Bool/β] = β[Int/α, Bool/β]
= Bool
[(α → β)/α][Int/α, Bool/β] = [(Int → Bool)/α, Bool/β]
Komposition ist assoziativ, σ1 (σ2 σ3 ) = (σ1 σ2 )σ3 ,
aber nicht kommutativ σ1 σ2 6= σ2 σ1
Als neutrales Element der Komposition haben wir die leere
Identitätssubstitution id = []. Es gilt id σ = σ und σ id = σ.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Typsubstitutionen Prinzipale Typisierung Inferenzprobleme
Prinzipale Typisierung
Eine Typisierung Γ ` e :: A eines Ausdrucks e ist die allgemeinste
oder auch prinzipal Typisierung, falls es für jede Typisierung
Γ0 ` e :: A0 von e eine Substitution σ gibt, so dass A0 = Aσ und
Γ0 ⊇ Γσ.
{x::α, f ::α → β}
{x::Int, f ::Int → Int}
{x::α, f ::α → β, y ::γ}
{x::γ, f ::γ → β}
`
`
`
`
f
f
f
f
x
x
x
x
::
::
::
::
β
prinzipal
Int σ = [Int/α, Int/β]
β
σ = id
β
σ = [γ/α] (auch prinzipal)
Mann kann beweisen, dass jeder Programmausdruck unserer
eingeschränkten Sprache eine prinzipale Typisierung hat!
Für volles Haskell gilt dies aber nicht mehr.
Die Berechnung einer prinzipalen Typisierung für einen
Programmausdruck ist oft unentscheidbar.
unentscheidbar: kein Verfahren mit endlicher Laufzeit bekannt
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Typsubstitutionen Prinzipale Typisierung Inferenzprobleme
Monomorphismus Einschränkung
In Haskell ist die Suche nach allgemeinen Typen grundsätzlich
eingeschränkt:
> :t show
show :: Show a => a -> String
> let f1 = show
f1 :: () -> String
> let f2 x = show x
f2 :: Show a => a -> String
> let f3 = \x -> show x
f3 :: () -> String
Option NoMonomorphismRestriction erweitert die Suche:
> :set -XNoMonomorphismRestriction
> let f4 = show
f4 :: Show a => a -> String
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Typsubstitutionen Prinzipale Typisierung Inferenzprobleme
Monomorphismus Einschränkung
Diese Option hat aber auch weitere Nachteile:
lenTwo = (len,len)
where
len = Data.List.genericLength xs
Für den Typ lenTwo :: Num t => [b] -> (t, t) muss die
Länge nur einmal berechnet werden, aber
für den Typ lenTwo :: (Num a, Num b) => [c] -> (a,b)
muss die Länge zwei Mal berechnet werden, da die grundlegenden
Operationen (z.B. Addition) ja anders implementiert sein könnten.
Wenn man Typsignaturen explizit angibt, dann braucht man die
Option ohnehin nicht, da Haskell dann einfach nur den gegeben
Typ prüft.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Typsubstitutionen Prinzipale Typisierung Inferenzprobleme
Polymorphe Rekursion
Polymorphe Rekursion bezeichnet rekursive Aufrufe mit anderem Typ;
genauer: polymorphe Typparameter werden anders instantiiert.
Polymorphe Rekursion ist sehr problematisch für Typinferenz. Es wurde
bewiesen, dass Typinferenz für polymorphe Rekursion unentscheidbar ist.
Beispiel
data PowerList a = Zero a | Succ (PowerList (a,a))
pl1 = Succ $ Succ $ Succ $ Zero (((1,2),(3,4)),((5,6),(7,8)))
plLength :: PowerList a -> Int
-- GHC cannot infer this type!
plLength (Zero _ ) = 1
plLength (Succ pl) = 2 * plLength pl
Im Rumpf der Definition für plLength :: PowerList a -> Int findet
ein rekursiver Aufruf mit Typ PowerList (a,a) -> Int statt.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Typsubstitutionen Prinzipale Typisierung Inferenzprobleme
Rank N-Types
Die Spracherweiterungen um GADTs und Rank N-Types bereiten
der Typinferenz ebenfalls Probleme Beide Themen gehen jedoch
über den Rahmen der Vorlesung heraus.
Rank N-Types sind Typen, bei denen Quantoren für Typvariablen
mitten im Typ auftauchen, und nicht nur wie bisher ganz vorne.
Pathologisches Beispiel
-- foo :: (forall a . a) -> b
foo :: (forall a . a -> a) -> (b -> b)
foo x = x x
> (foo (foo id)) 42
42
Keine der beiden Typsignaturen kann von GHC inferiert werden.
foo hat keinen prinzipalen Typ.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Unifikation Zusammenfassung
Typinferenz
Typinferenz berechnet zu jedem Ausdruck den allgemeinsten Typ
(bzw. Fehlermeldung, falls der Ausdruck keinen Typ hat).
Beispiel
Der prinzipale Typ einer Funktionsanwendung e1 e2 muss aus den
prinzipalen Typen von e1 und e2 berechnet werden:
{} ` e1 :: (Int → β) → (γ → β)
{x::α} ` e2 :: α → Double
{x::Int} ` e1 e2 :: γ → Double
Beide Prämissen müssen mit [Int/α, Double/β] instanziiert
werden, welche die allgemeinste Lösung der Gleichung
Int → β = α → Double ist.
Dazu müssen wir also Typgleichung lösen können.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Unifikation Zusammenfassung
Unifikation
Eine Substitution σ ist allgemeiner als σ 0 , falls es eine
Substitution τ gibt mit σ 0 = στ .
Die speziellere Substitution σ 0 ist also eine Instanz von σ.
Zum Lösen von Typgleichungen verwenden wir Unifikation:
Ein Unifikator zweier Typen A und A0 ist eine Substitution σ,
so dass Aσ = A0 σ.
Der allgemeinste Unifikator von A und A0 ist die
allgemeinste Substitution σ, so dass Aσ = A0 σ.
Beispiel:
(α → β) = (Int → β)
für diese Typgleichung ist [Int/α, Int/β] ein Unifikator. Die
Substitution [Int/α] ist der allgemeinste Unifikator.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Unifikation Zusammenfassung
Robinson’s Unifikationsalgorithmus
Der Unifikationsalgorithmus unify arbeitet auf einer Menge
E = {A1 = B1 , . . . , An = Bn } von Typgleichungen und liefert
allgemeinste Substitution σ, so dass Ai σ = Bi σ für alle i gilt
oder einen Typfehler.
Algorithmus unify:
1
unify({}) = id
2
unify({A = A} ] E ) = unify(E )
A0
- -Fertig
=B→
B 0 }]E )
- -redundante Gleichung
= unify({A = B, A0 = B 0 }]E )
3
unify({A →
4
unify({α = B} ] E ) = unify({B = α} ] E ) =
falls α in B erwähnt wird: Fehlermeldung “Zirkulär”,
sonst berechne Komposition σ unify(E σ) für σ = [B/α]
5
In allen andern Fällen: Fehlermeldung “Typfehler”
z.B. wenn (Int = Bool) oder (Int = α → β) E sind.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Unifikation Zusammenfassung
Beispiele Unifikation
Nur Variablen:
unify{α = β, β = γ} = [β/α]unify{β = γ} = [β/α][γ/β]unify{}
= [β/α][γ/β]id
= [β/α][γ/β]
= [γ/α, γ/β]
Typkonstruktorkonflikt:
unify{Int → α = α → Double} = unify{Int = α, α = Double}
= [Int/α]unify{Int = Double} = ”Error: Typfehler”
Zirkulärer Typ:
unify{β = β, α = α → Double} = unify{α = α → Double}
= ”Error: Zirkulär”
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Unifikation Zusammenfassung
Typinferenzalgorithmus I
inferΓ (e) = (A, σ) nimmt einen Ausdruck e in Typkontext Γ und
liefert den allgemeinsten Typ A von e samt der allgemeinsten
Instanziierung σ von Γ, so dass Γσ ` e :: A.
1
inferΓ (x) = wenn x::A ∈ Γ dann (A, id)
sonst Fehlermeldung “Ungebundene Variable”
2
inferΓ (c) = (A, id) wobei A der Typ der Konstanten c ist.
3
inferΓ (\x -> e) =
Für eine frische Typvariable α berechne zuerst
inferΓ,x::α (e) = (B, σ)
Damit gilt (Γσ, x::ασ) ` e :: B und wir liefern (ασ → B, σ)
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Unifikation Zusammenfassung
Typinferenzalgorithmus II
Applikation
inferΓ (e) = (A, σ) nimmt einen Ausdruck e in Typkontext Γ und
liefert den allgemeinsten Typ A von e samt der allgemeinsten
Instanziierung σ von Γ, so dass Γσ ` e :: A.
4 inferΓ (e1 e2 ) =
Wir berechnen zuerst inferΓ (e1 ) = (C , σ1 ) und
inferΓ (e2 ) = (A, σ2 ).
Für eine frische Typvariable β berechne
σ3 = unify{C σ 0 = A → β}.
Damit gilt Γσ1 σ2 σ3 ` e1 :: Aσ3 → βσ3 und wir liefern
(βσ3 , σ1 σ2 σ3 ) zurück.
Dieser Algorithmus geht auf mehrere Personen zurück:
Ursprung bei Curry & Feys (1958)
Erweitert und Allgemeinheit bewiesen bei Hindley (1969)
Milner zeigte 1979 unabhängig das Geliche wie Hindley
Erweitert unf Vollstänsigkeit bewiesen von Damas (1982)
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Typsysteme Typisierung Polymorphie Typinferenz
Unifikation Zusammenfassung
Zusammenfassung
Well-typed programs can’t go wrong
Robin Milner
Typisierung in Form von Typurteilen: Γ ` e::A.
Kontrolle eines Typs durch Typherleitung.
Eine Typherleitung hat eine Baum-Struktur.
Wenn alle Blätter Axiome sind, dann ist die Herleitung ein
Beweis.
Typinferenz beruht auf Unifikation.
Prinzipale Typen in vielen Sprache berechenbar.
Steffen Jost
Einführung in die Funktionale Programmierung mit Haskell
Herunterladen