Typen, Typberechnung und Typcheck

Werbung
Haskell, Typen, und Typberechnung
Grundlagen der Programmierung 3 A
Ziele:
Typen, Typberechnung und Typcheck
Prof. Dr. Manfred Schmidt-Schauß
•
Haskells Typisierung
•
Typisierungs-Regeln
•
Typ-Berechnung
•
Milners Typcheck (Robin Milner)
Sommersemester 2017
Grundlagen der Programmierung 2 (Typen-A)
1
Einige andere Programmiersprachen
Typisierung in Haskell
ab Java 5: generische Typen verwandt mit polymorphen
Typen
Haskell hat eine starke, statische Typisierung.
mit parametrisch polymorphen Typen.
ML (und CAML, OCAML) hat parametrisch polymorphes
Typsystem
•
jeder Ausdruck muss einen Typ haben
JavaScript Nachteile: Es fehlt eine statische Typisierung
•
Der Typchecker berechnet Typen aller Ausdrücke
und prüft Typen zur Compilezeit
•
Es gibt keine Typfehler zur Laufzeit
d.h. kein dynamischer Typcheck nötig.
TypeScript (Microsoft): Neue JavaScript Variante mit
statischer Typisierung und Typinferenz.
Google Ankündigung: Angular 2 (JavaScript-Webframework)
zukünftig auf Basis von TypeScript
Grundlagen der Programmierung 2 (Typen-A)
– 2/37 –
– 3/37 –
Grundlagen der Programmierung 2 (Typen-A)
– 4/37 –
Typisierung, Begriffe
Überladung und Konversion in Haskell
Haskell:
polymorph:
keine Typkonversion
Es gibt Überladung:
z.B. arithmetische Operatoren: +, −.∗, /
parametrisch Die (i.a. unendliche) Menge der Typen
polymorph:
entspricht einem schematischen Typausdruck
Zahlkonstanten für ganze Zahlen sind überladen
Beispiel
Typcheck
erstmal ohne Überladung
ohne Kinds
und ohne Typen höherer Ordnung
Grundlagen der Programmierung 2 (Typen-A)
Ein Ausdruck kann viele Typen haben
(vielgestaltig, polymorph).
– 5/37 –
Syntax von Typen in Haskell (ohne Typklassen)
length
Schema:
[a]->Int
Instanzen:
[Int]->Int,
usw.
[Char]->Int,
[[Char]]->Int
Grundlagen der Programmierung 2 (Typen-A)
– 6/37 –
Syntax von Typen
Typkonstruktoren können benutzerdefiniert sein (z.B. Baum a)
hTypi
::= hBasistypi | (hTypi) | hTypvariablei
Vordefinierte
Typkonstruktoren:
| hTypkonstruktorn i{hTypi}n
(n = Stelligkeit)
hBasistypi ::= Int | Integer | Float | Rational | Char | . . .
Grundlagen der Programmierung 2 (Typen-A)
– 7/37 –
Liste
Tupel
Funktionen
[·]
(.,...,.)
· →·
(Stelligkeit 2, Infix)
Konvention zu Funktionstypen mit →
a → b → c → d bedeutet: a → (b → (c → d)).
Grundlagen der Programmierung 2 (Typen-A)
– 8/37 –
Interpretation der Typen
Komposition
Beispiel: Komposition von Funktionen:
komp::(a -> b) -> (c -> a) -> c -> b
komp f g x = f (g x)
length :: [a] -> Int
Interpretation:
Für alle Typen typ ist length eine Funktion,
die vom Typ [typ] → Int ist.
In Haskell ist komp vordefiniert und wird als .“ geschrieben:
”
Beispielaufruf:
*Main> suche_nullstelle (sin . quadrat) 1 4 0.00000001
←-
1.772453852929175
(sin . quadrat) entspricht sin(x2 )
und quadrat . sin entspricht (sin(x))2 .
Grundlagen der Programmierung 2 (Typen-A)
– 9/37 –
Typ der Komposition
(c->a) ->
Typ
Typ
Typ
Typ
c->b
(τ3 -> τ1 )
τ3
τ2
(τ3 -> τ2 )
x :: τ3
Grundlagen der Programmierung 2 (Typen-A)
g
– 10/37 –
Typen von Konstruktoren
Erklärung zum Typ von komp, wobei {a,b,c} Typvariablen sind.
Ausdruck: f ‘komp‘ g bzw. f . g
(a->b) ->
(τ1 -> τ2 )
Grundlagen der Programmierung 2 (Typen-A)
/
τ1
von (.)
von f
von g
des Arguments x
der Komposition f . g
Typ des Resultats
der Komposition f (g x)
Typ von (f . g)
f
/
Typen von Konstruktoren werden durch deren data-Definition
automatisch festgelegt!
data Baum a b = Empty | Blatt b
| Knoten a (Baum a b) Baum a b)
Typen der
Empty
Blatt
Knoten
Konstruktoren:
:: Baum a b
:: b → Baum a b
:: b → Baum a b → Baum a b → Baum a b
τ2
– 11/37 –
Grundlagen der Programmierung 2 (Typen-A)
– 12/37 –
Typregeln
Typregel Anwendung“: mehrere Argumente
”
Wie berechnet man Typen von Ausdrücken?
Anwendung von Funktionsausdruck auf Argument
s :: σ1 → σ2 . . . → σn → τ , t1 :: σ1 , . . . , tn :: σn
(s t1 . . . tn ) :: τ
s :: σ → τ , t :: σ
(s t) :: ?
Beispiele
Beispiele:
quadrat :: Int → Int ,
(quadrat 2) :: ?
+ :: Int → (Int → Int)
(1 +) :: ?
+ :: Int → Int → Int, 1 :: Int , 2 :: Int
(+ 1 2) :: Int
2 :: Int
,
+ :: Int → Int → Int
(1 + 2) :: Int
1 :: Int
,
2:: Int
((1 +) 2) :: ?
Grundlagen der Programmierung 2 (Typen-A)
– 13/37 –
Beispiel
(c -> a) ->
quadrat::Int -> Int
c -> b
.
a -> b = Int -> Bool
.
.
c = Int; b = Bool
.
c -> a = Int -> Int
Ziel: Anwendung der Typregel für z.B. length oder map
Neuer Begriff: γ ist eine Typsubstitution
wenn sie Typen für Typvariablen einsetzt
γ(τ ) nennt man Instanz von τ
Beispiel
Typsubstitution:
Instanz:
even . quadrat :: Int -> Bool
Grundlagen der Programmierung 2 (Typen-A)
– 14/37 –
Anwendungsregel für polymorphe Typen
even::Int -> Bool
(.)::(a -> b) ->
Grundlagen der Programmierung 2 (Typen-A)
– 15/37 –
Grundlagen der Programmierung 2 (Typen-A)
γ = {a 7→ Char, b 7→ Float}
γ([a] → Int) = [Char] → Int
– 16/37 –
Anwendungsregel für polymorphe Typen
Beispiel zur polymorphen Anwendungsregel
Typ von (map quadrat) ?
s :: σ → τ, t :: ρ und γ(σ) = γ(ρ)
(s t) :: γ(τ )
map ::
(a → b) → ([a] → [b])
quadrat :: Int
→ Int
Berechnet den Typ von (s t)
wenn die Typen von s, t schon bekannt sind
Instanziiere mit:
ergibt:
Hierbei ist zu beachten:
Damit alle Freiheiten erhalten bleiben:
Regelanwendung ergibt:
die Typvariablen in ρ müssen vorher umbenannt werden,
so dass σ und ρ keine gemeinsamen Typvariablen haben.
Grundlagen der Programmierung 2 (Typen-A)
map :: (Int → Int) → ([Int] → [Int]), quadrat :: (Int → Int)
(map quadrat) :: [Int] → [Int]
– 17/37 –
Polymorphe Regel für n Argumente
Grundlagen der Programmierung 2 (Typen-A)
– 18/37 –
Wie berechnet man die Typsubstitutionen?
s :: σ1 → σ2 . . . → σn → τ, t1 :: ρ1 , . . . , tn :: ρn und ∀i : γ(σi ) = γ(ρi )
(s t1 . . . tn ) :: γ(τ )
Die Typvariablen in ρ1 , . . . , ρn müssen vorher umbenannt werden.
Beachte:
γ = {a 7→ Int, b 7→ Int}
map :: (Int → Int) → ([Int] → [Int])
Unifikation:
Berechnung der allgemeinsten Typsubstitution
im Typberechnungsprogramm bzw Typchecker
Unifikation wird benutzt im Typchecker von Haskell!
verwende möglichst allgemeines γ
(kann berechnet werden; s.u.)
Grundlagen der Programmierung 2 (Typen-A)
– 19/37 –
Grundlagen der Programmierung 2 (Typen-A)
– 20/37 –
polymorphe Typen; Unifikation
Unifikation: Regelbasierter Algorithmus
Man braucht 4 Regeln, die auf (E; G) operieren:
s :: σ → τ ,
.
t :: ρ und γ ist allgemeinster Unifikator von σ = ρ
(s t) :: γ(τ )
E:
G:
Menge von Typgleichungen,
Lösung; mit Komponenten der Form x 7→ t.
Beachte:
(die Typvariablen in ρ müssen vorher umbenannt werden.)
Grundlagen der Programmierung 2 (Typen-A)
– 21/37 –
Unifikation: Die Regeln
x, y ist Typvariable
si , ti , s, t sind Typen,
f, g sind Typkonstruktoren
Grundlagen der Programmierung 2 (Typen-A)
– 22/37 –
Unifikation: Regelbasierter Algorithmus
Start mit G = ∅;
E: zu lösende Gleichung(en)
•
•
•
•
.
G; {x = x} ∪ E
G; E
.
G; {t = x} ∪ E
Wenn t keine Variable ist
.
G; {x = t} ∪ E
.
G; {x = t} ∪ E
Wenn x nicht in t vorkommt
G[t/x] ∪ {x 7→ t}; E[t/x]
.
G; {(f s1 . . . sn ) = (f t1 . . . tn )} ∪ E
.
.
G; {s1 = t1 , . . . , sn = tn )} ∪ E
Ersetzung:
Effekt:
Fehlerabbruch, wenn:
.
• x = t in E, x 6= t und x kommt in t vor. (Occurs-Check)
•
.
(f (. . .) = g(. . .) kommt in E vor und f 6= g. (Clash)
Fehlerabbruch bedeutet: nicht typisierbar
E[t/x]: alle Vorkommen von x werden durch t ersetzt
G[t/x]: jedes y 7→ s wird durch y 7→ s[t/x] ersetzt
Grundlagen der Programmierung 2 (Typen-A)
– 23/37 –
Grundlagen der Programmierung 2 (Typen-A)
– 24/37 –
Beispiel mit Typvariablen
Noch ein Beispiel
Berechne Typ von (map id)
map::
id::
Gesuchter Typ:
(a → b)
a0 → a0
Berechne Typ von (map head)
→ ([a] → [b])
map::
head::
Gesuchter Typ:
γ([a] → [b])
.
Regelanwendung benötigt Lösung γ von (a → b) = (a0 → a0 ):
G
∅;
∅;
{a 7→ a0 };
{a 7→ a0 , b 7→ a0 };
{a 7→ a0 , b 7→ a0 };
– 25/37 –
Grundlagen der Programmierung 2 (Typen-A)
– 26/37 –
Beispiel. Linksfaltung: (foldl (:) [])?
foldr :: (a -> b -> b) -> b -> [a] -> b
(:) :: a -> [a] -> [a]
umbenannt: c -> [c] -> [c]
umbenannt: [d]
([]) :: [a]
Grundlagen der Programmierung 2 (Typen-A)
E
.
{(a → b) = ([a0 ] → a0 )}
.
.
{a = [a0 ], b = a0 }
. 0
{b = a }
∅
Einsetzen der Lösung γ = {a 7→ [a0 ], b 7→ a0 }
in [a] → [b] ergibt:
0
0
(map head) :: ([[a ]] → [a ]).
Beispiel (foldr (:) []) :: ??
(foldr (:) [])::[c] → [c]
γ([a] → [b])
G
∅;
∅;
{a 7→ [a0 ]};
{a 7→ [a0 ], b 7→ a0 };
{a 7→ [a0 ], b 7→ a0 };
Einsetzen der Lösung γ = {a 7→ a0 , b 7→ a0 }
in [a] → [b] ergibt:
0
0
(map id) :: ([a ] → [a ]).
G
∅
{b 7→ [d]}
{b 7→ [d]}
{b 7→ [d], a 7→ c}
{b 7→ [d], a 7→ c}
{b 7→ [c], a 7→ c, d 7→ c}
{b 7→ [c], a 7→ c, d 7→ c}
{b 7→ [c], a 7→ c, d 7→ c}
→ ([a] → [b])
.
Regelanwendung benötigt Lösung γ von (a → b) = ([a0 ] → a0 ):
E
.
{(a → b) = (a0 → a0 )}
.
.
{a = a0 , b = a0 }
. 0
{b = a }
∅
Grundlagen der Programmierung 2 (Typen-A)
(a → b)
[a] → a
foldl :: (a -> b -> a) -> a -> [b] -> a
(:) :: a -> [a] -> [a]
umbenannt: c -> [c] -> [c]
umbenannt: [d]
([]) :: [a]
E
.
.
{a → b → b = c → [c] → [c], b = [d]}
.
{a → [d] → [d] = c → [c] → [c]}
.
.
.
{a = c, [d] = [c], [d] = [c]}
.
.
{[d] = [c], [d] = [c]}
.
.
{d = c, [d] = [c]}
.
{[c] = [c]}
.
{c = c}
{}
= γ([a] → b)
G
∅
{a 7→ [d]}
{a 7→ [d]}
{a 7→ [d], b 7→ [c]}
{a 7→ [d], b 7→ [c]}
{a 7→ [d], b 7→ [[d]], c 7→ [d]}
{a 7→ [d], b 7→ [[d]], c 7→ [d]}
nicht lösbar, da d in [d] echt
E
.
.
{a → b → a = c → [c] → [c], a = [d]}
.
{[d] → b → [d] = c → [c] → [c]}
.
.
.
{[d] = c, b = [c], [d] = [c]}
.
.
{[d] = c, [d] = [c]}
.
.
{c = [d], [d] = [c]}
.
{[d] = [[d]]}
.
{d = [d]}
vorkommt
(foldl (:) []) ist nicht typisierbar!
– 27/37 –
Grundlagen der Programmierung 2 (Typen-A)
– 28/37 –
Beispiel zu Typberechnung
Beispiele zu polymorpher Typberechnung
Typ von map length
map :: (a → b) → ([a] → [b]), length :: [a0 ] → Int
(map length) :: ? = γ([a] → [b])
Berechne Typ der Liste [1]:
1 : Int
.
Unifiziere (a → b) = [a0 ] → Int
G
∅;
∅;
{a 7→ [a0 ]};
{a 7→ [a0 ], b 7→ Int};
(:) :: a → [a] → [a]
1 : [] ::?
[] :: [b]
.
.
Anwendungsregel ergibt Gleichungen: {a = Int, [a] = [b]}
E
.
{(a → b) = ([a0 ] → Int)}
.
.
{a = [a0 ], b = Int}
.
{b = Int}
∅
Lösung: γ = {a 7→ Int}
Typ (1 : []) :: [Int]
Somit: (map length) :: γ([a] → [b]) = [[a0 ]] → [Int]
Grundlagen der Programmierung 2 (Typen-A)
– 29/37 –
Beispiel zu Typfehler
[1, ’a’] hat keinen Typ:
• 1 : ( ’a’ : [])
• 1 :: Integer, [] :: [b], ’a’ :: Char
• (1 :) :: [Integer] → [Integer] und
(’a’:[]) :: [Char].
– 30/37 –
Beispiel zu Typfehler im Interpreter
Prelude> [1,’a’] ←-
(Typen der Konstanten.)
Kein Typ als Resultat, denn:
.
[Integer] = [Char] ist nicht lösbar.
Grundlagen der Programmierung 2 (Typen-A)
Grundlagen der Programmierung 2 (Typen-A)
– 31/37 –
<interactive>:1:1:
No instance for (Num Char)
arising from the literal ‘1’ at <interactive>:1:1
Possible fix: add an instance declaration for (Num Char)
In the expression: 1
In the expression: [1, ’a’]
In the definition of ‘it’: it = [1, ’a’]
Grundlagen der Programmierung 2 (Typen-A)
– 32/37 –
Typ eines Ausdrucks
Typisierung von Funktionen und Ausdrücken
Kompexe Sprachkonstrukte:
Typ von (map quadrat [1,2,3,4]) :
• map::
(a → b) → [a] → [b]
rekursiv definierte Funktionen
Lambda-Ausdrücke, let-Ausdrücke
Listen-Komprehensionen.
Typcheckalgorithmus von Robin Milner (in Haskell und ML)
• ist schnell, (i.a.)
• liefert allgemeinste (Milner-)Typen
• benutzt Unifikation (eine optimierte Variante)
•
quadrat::
Integer → Integer, und [1, 2, 3, 4] :: [Integer].
•
γ=
{a 7→ Integer, b 7→ Integer}.
•
Das ergibt:
γ(a) = Integer, γ([a]) = [Integer], γ([b]) = [Integer].
•
•
Resultat:
γ([b]) = [Integer]
•
Grundlagen der Programmierung 2 (Typen-A)
– 33/37 –
Typisierung von Funktionen, Milner
Grundlagen der Programmierung 2 (Typen-A)
– 34/37 –
Typisierung und Reduktion
Beachte:
Satz zur Milner-Typisierung in Haskell:
Sei t ein getypter Haskell-Ausdruck, ohne freie Variablen.
Dann wird die Auswertung des Ausdrucks t
nicht mit einem Typfehler abbrechen.
Nach Reduktionen kann ein Ausdruck
mehr Typen (bzw. einen allgemeineren Typ) haben
als vor der Reduktion
Beispiel:
if 1 > 0 then [] else [1]
Allgemeine Aussage zu starken Typsystemen
Es gibt keine dynamischen Typfehler,
wenn das Programm statisch korrekt getypt ist
Grundlagen der Programmierung 2 (Typen-A)
schlechte worst-case Komplexität:
in seltenen Fällen exponentieller Zeitbedarf
Liefert in seltenen Fällen nicht
die allgemeinsten möglichen polymorphen Typen
– 35/37 –
:: [Integer]
arithmetische-Reduktion:
−→ if True then [] else [1]
:: [Integer]
Case-Reduktion:
−→ []
:: [a]
Grundlagen der Programmierung 2 (Typen-A)
– 36/37 –
Typisierung und Reduktion
weiteres Beispiel:
(if 1 > 0 then foldr else foldl) ::
(a -> a -> a) -> a -> [a] -> a
Reduktion ergibt als Resultat:
foldr:: (a -> b -> b) -> b -> [a] -> b
der Typ ist allgemeiner geworden!
Grundlagen der Programmierung 2 (Typen-A)
– 37/37 –
Herunterladen