Grundlagen der Programmierung 3 A [1.5ex] Typen, Typberechnung

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 2016
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/36 –
– 3/36 –
Grundlagen der Programmierung 2 (Typen-A)
– 4/36 –
Typisierung, Begriffe
Überladung und Konversion in Haskell
polymorph:
Haskell:
keine Typkonversion
parametrisch Die (i.a. unendliche) Menge der Typen
polymorph:
entspricht einem schematischen Typausdruck
Es gibt Überladung:
z.B. arithmetische Operatoren: +, −.∗, /
Beispiel
Zahlkonstanten für ganze Zahlen sind überladen
Grundlagen der Programmierung 2 (Typen-A)
Ein Ausdruck kann viele Typen haben
(vielgestaltig, polymorph).
– 5/36 –
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/36 –
Syntax von Typen
Typkonstruktoren können benutzerdefiniert sein (z.B. Baum a)
hTypi
::= hBasistypi | (hTypi) | hTypvariablei
Vordefinierte
Typkonstruktoren:
| hTypkonstruktorn i{hTypi}n
(n = Stelligkeit)
[·]
(.,...,.)
· →·
(Stelligkeit 2, Infix)
Konvention zu Funktionstypen mit →
a → b → c → d bedeutet: a → (b → (c → d)).
hBasistypi ::= Int | Integer | Float | Rational | Char
Grundlagen der Programmierung 2 (Typen-A)
Liste
Tupel
Funktionen
– 7/36 –
Grundlagen der Programmierung 2 (Typen-A)
– 8/36 –
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/36 –
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/36 –
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/36 –
Grundlagen der Programmierung 2 (Typen-A)
– 12/36 –
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/36 –
Beispiel
(c -> a) ->
quadrat::Int -> Int
c -> b
.
a -> b = Int -> Bool
.
c -> b = Int -> 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/36 –
Anwendungsregel für polymorphe Typen
even::Int -> Bool
(.)::(a -> b) ->
Grundlagen der Programmierung 2 (Typen-A)
– 15/36 –
Grundlagen der Programmierung 2 (Typen-A)
γ = {a 7→ Char, b 7→ Float}
γ([a] → Int) = [Char] → Int
– 16/36 –
Anwendungsregel für polymorphe Typen
Beispiel zur polymorphen Anwendungsregel
Typ von (map quadrat) ?
s :: σ → τ, t :: ρ und γ(σ) = γ(ρ)
(s t) :: γ(τ )
map :: (a → b) → ([a] → [b])
Berechnet den Typ von (s t) wenn Typen von s, t schon bekannt
sind
Instanziiere mit:
ergibt:
γ = {a 7→ Int, b 7→ Int}
map :: (Int → Int) → ([Int] → [Int])
Regelanwendung ergibt:
Hierbei ist zu beachten:
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/36 –
Polymorphe Regel für n Argumente
– 18/36 –
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:
Grundlagen der Programmierung 2 (Typen-A)
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/36 –
Grundlagen der Programmierung 2 (Typen-A)
– 20/36 –
polymorphe Typen; Unifikation
Unifikation: Regelbasierter Algorithmus
Man braucht 4 Regeln, die auf (E; G) operieren:
s :: σ → τ ,
.
t :: ρ und γ ist allgemeinster Unifikator von σ = ρ
(s t) :: γ(τ )
Menge von Typgleichungen,
Lösung; mit Komponenten der Form x 7→ t.
Beachte; x sind Typvariablen, t sind Typen, f, g sind
Typkonstruktoren
(die Typvariablen in ρ müssen vorher umbenannt werden.)
Grundlagen der Programmierung 2 (Typen-A)
E:
G:
– 21/36 –
Unifikation: Die Regeln
Grundlagen der Programmierung 2 (Typen-A)
– 22/36 –
Unifikation: Regelbasierter Algorithmus
Start mit G = ∅; E
•
•
•
•
.
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.
•
.
(f (. . .) = g(. . .) kommt in E vor und f 6= g.
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/36 –
Grundlagen der Programmierung 2 (Typen-A)
– 24/36 –
Beispiel mit Typvariablen
Beispiel (foldr (:) []) :: ??
Berechne Typ von (map id)
map::
id::
Gesuchter Typ:
(a → b)
a0 → a0
foldr :: (a -> b -> b) -> b -> [a] -> b
umbenannt: c -> [c] -> [c]
(:) :: a -> [a] -> [a]
umbenannt: [d]
([]) :: [a]
→ ([a] → [b])
γ([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 };
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}
E
.
{(a → b) = (a0 → a0 )}
.
.
{a = a0 , b = a0 }
. 0
{b = a }
∅
Einsetzung der Lösung γ = {a 7→ a0 , b 7→ a0 }
in [a] → [b] ergibt:
0
0
(map id) :: ([a ] → [a ]).
Grundlagen der Programmierung 2 (Typen-A)
(foldr (:) [])::[c] → [c]
– 25/36 –
Beispiel. Linksfaltung: (foldl (:) [])?
= γ([a] → b)
Grundlagen der Programmierung 2 (Typen-A)
– 26/36 –
Beispiel zu Typberechnung
Typ von map length
foldl :: (a -> b -> a) -> a -> [b] -> a
(:) :: a -> [a] -> [a]
umbenannt: c -> [c] -> [c]
umbenannt: [d]
([]) :: [a]
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 → 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}
{}
map :: (a → b) → ([a] → [b]), length :: [a0 ] → Int
(map length) :: ? = γ([a] → [b])
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
.
Unifiziere (a → b) = [a0 ] → Int
G
∅;
∅;
{a 7→ [a0 ]};
{a 7→ [a0 ], b 7→ Int};
E
.
{(a → b) = ([a0 ] → Int)}
. 0
.
{a = [a ], b = Int}
.
{b = Int}
∅
Somit: (map length) :: γ([a] → [b]) = [[a0 ]] → [Int]
(foldl (:) []) ist nicht typisierbar!
Grundlagen der Programmierung 2 (Typen-A)
– 27/36 –
Grundlagen der Programmierung 2 (Typen-A)
– 28/36 –
Beispiele zu polymorpher Typberechnung
Beispiel zu Typfehler
Berechne Typ der Liste [1]:
1 : Int
(:) :: a → [a] → [a]
1 : [] ::?
[1, ’a’] hat keinen Typ:
• 1 : ( ’a’ : [])
• 1 :: Integer, [] :: [b], ’a’ :: Char
• (1 :) :: [Integer] → [Integer] und
(’a’:[]) :: [Char].
[] :: [b]
.
.
Anwendungsregel ergibt Gleichungen: {a = Int, [a] = [b]}
Lösung: γ = {a 7→ Int}
Kein Typ als Resultat, denn:
.
[Integer] = [Char] ist nicht lösbar.
Typ (1 : []) :: [Int]
Grundlagen der Programmierung 2 (Typen-A)
– 29/36 –
Beispiel zu Typfehler im Interpreter
Grundlagen der Programmierung 2 (Typen-A)
– 30/36 –
Typ eines Ausdrucks
Typ von (map quadrat [1,2,3,4]) :
• map::
(a → b) → [a] → [b]
Prelude> [1,’a’] ←<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)
(Typen der Konstanten.)
– 31/36 –
•
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)
– 32/36 –
Typisierung von Funktionen und Ausdrücken
Typisierung von Funktionen, Milner
Kompexe Sprachkonstrukte:
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)
•
•
Es gibt keine dynamischen Typfehler,
wenn das Programm statisch korrekt getypt ist
– 33/36 –
Typisierung und Reduktion
Beachte:
Grundlagen der Programmierung 2 (Typen-A)
– 34/36 –
Typisierung und Reduktion
Nach Reduktionen kann ein Ausdruck
mehr Typen (bzw. einen allgemeineren Typ) haben
als vor der Reduktion
Beispiel:
if 1 > 0 then [] else [1]
Sei t ein getypter Haskell-Ausdruck, ohne freie Variablen.
Dann wird die Auswertung des Ausdrucks t
nicht mit einem Typfehler abbrechen.
Allgemeine Aussage zu starken Typsystemen
schlechte worst-case Komplexität:
in seltenen Fällen exponentieller Zeitbedarf
Liefert in seltenen Fällen nicht
die allgemeinsten möglichen polymorphen Typen
Grundlagen der Programmierung 2 (Typen-A)
Satz zur Milner-Typisierung in Haskell:
weiteres Beispiel:
(if 1 > 0 then foldr else foldl) ::
(a -> a -> a) -> a -> [a] -> a
:: [Integer]
Reduktion ergibt als Resultat:
arithmetische-Reduktion:
−→ if True then [] else [1]
Case-Reduktion:
−→ []
Grundlagen der Programmierung 2 (Typen-A)
foldr:: (a -> b -> b) -> b -> [a] -> b
:: [Integer]
der Typ ist allgemeiner geworden!
:: [a]
– 35/36 –
Grundlagen der Programmierung 2 (Typen-A)
– 36/36 –
Herunterladen