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 2015
1
Grundlagen der Programmierung 2 (Typen-A)
Typisierung in Haskell
– 2/34 –
Überladung und Konversion in Haskell
Haskell hat eine starke, statische Typisierung.
mit parametrisch polymorphen Typen.
•
jeder Ausdruck muss einen Typ haben
•
Der Typchecker berechnet Typen aller Ausdrücke
und prüft Typen zur Compilezeit
•
Haskell:
keine Typkonversion
Es gibt Überladung:
z.B. arithmetische Operatoren: +, −.∗, /
ganze Zahlkonstanten sind überladen
Es gibt keine Typfehler zur Laufzeit
d.h. kein dynamischer Typcheck nötig.
Grundlagen der Programmierung 2 (Typen-A)
– 3/34 –
Grundlagen der Programmierung 2 (Typen-A)
– 4/34 –
Typisierung, Begriffe
polymorph:
Syntax von Typen in Haskell (ohne Typklassen)
Ein Ausdruck kann viele Typen haben
(vielgestaltig, polymorph).
hTypi
parametrisch Die (i.a. unendliche) Menge der Typen
polymorph:
entspricht einem schematischen Typausdruck
Beispiel
::= hBasistypi | (hTypi) | hTypvariablei
| hTypkonstruktorn i{hTypi}n
(n = Stelligkeit)
length
Schema:
[a]->Int
Instanzen:
[Int]->Int,
usw.
hBasistypi ::= Int | Integer | Float | Rational | Char
[Char]->Int,
[[Char]]->Int
Grundlagen der Programmierung 2 (Typen-A)
– 5/34 –
Syntax von Typen
Grundlagen der Programmierung 2 (Typen-A)
– 6/34 –
Interpretation der Typen
Typkonstruktoren können benutzerdefiniert sein (z.B. Baum a)
Vordefinierte
Typkonstruktoren:
Liste
Tupel
Funktionen
[·]
(.,...,.)
· →·
(Stelligkeit 2, Infix)
length :: [a] -> Int
Interpretation:
Konvention zu Funktionstypen mit →
a → b → c → d bedeutet: a → (b → (c → d)).
Grundlagen der Programmierung 2 (Typen-A)
– 7/34 –
Grundlagen der Programmierung 2 (Typen-A)
Für alle Typen typ ist length eine Funktion,
die vom Typ [typ] → Int ist.
– 8/34 –
Komposition
Typ der Komposition
Beispiel: Komposition von Funktionen:
Erklärung zum Typ von komp, wobei {a,b,c} Typvariablen sind.
Ausdruck: f ‘komp‘ g bzw. f . g
komp::(a -> b) -> (c -> a) -> c -> b
komp f g x = f (g x)
(a->b) ->
(τ1 -> τ2 )
(c->a) ->
Typ
Typ
Typ
Typ
c->b
(τ3 -> τ1 )
In Haskell ist komp vordefiniert und wird als .“ geschrieben:
”
Beispielaufruf:
τ3
τ2
*Main> suche_nullstelle (sin . quadrat) 1 4 0.00000001
←-
(τ3 -> τ2 )
1.772453852929175
x :: τ3
g
/
τ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
/
τ2
sin(x2 )
(sin . quadrat) entspricht
und quadrat . sin entspricht (sin(x))2 .
Grundlagen der Programmierung 2 (Typen-A)
– 9/34 –
Typregeln
Grundlagen der Programmierung 2 (Typen-A)
– 10/34 –
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)
– 11/34 –
Grundlagen der Programmierung 2 (Typen-A)
– 12/34 –
Beispiel
Anwendungsregel für polymorphe Typen
even::Int -> Bool
(.)::(a -> b) ->
(c -> a) ->
quadrat::Int -> Int
c -> b
.
a -> b = Int -> Bool
.
c -> b = Int -> Bool
.
c -> a = Int -> Int
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)
Ziel: Anwendung der Typregel für z.B. length oder map
– 13/34 –
Anwendungsregel für polymorphe Typen
γ = {a 7→ Char, b 7→ Float}
γ([a] → Int) = [Char] → Int
Grundlagen der Programmierung 2 (Typen-A)
– 14/34 –
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]
– 15/34 –
Grundlagen der Programmierung 2 (Typen-A)
– 16/34 –
Polymorphe Regel für n Argumente
Wie berechnet man die Typsubstitutionen?
s :: σ1 → σ2 . . . → σn → τ, t1 :: ρ1 , . . . , tn :: ρn und ∀i : γ(σi ) = γ(ρi )
(s t1 . . . tn ) :: γ(τ )
Unifikation:
Unifikation wird benutzt im Typchecker von Haskell!
Die Typvariablen in ρ1 , . . . , ρn müssen vorher umbenannt werden.
Beachte:
Berechnung der allgemeinsten Typsubstitution
im Typberechnungsprogramm bzw Typchecker
verwende möglichst allgemeines γ
(kann berechnet werden; s.u.)
Grundlagen der Programmierung 2 (Typen-A)
– 17/34 –
polymorphe Typen; Unifikation
Grundlagen der Programmierung 2 (Typen-A)
– 18/34 –
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:
– 19/34 –
Grundlagen der Programmierung 2 (Typen-A)
– 20/34 –
Unifikation: Die Regeln
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:
•
.
(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)
– 21/34 –
Beispiel mit Typvariablen
(a → b)
a0 → a0
.
Regelanwendung benötigt Lösung von (a → b) = (a0 → 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 )}
. 0 . 0
{a = a , b = a }
.
{b = a0 }
∅
Einsetzung der Lösung γ = {a 7→ a0 , b 7→ a0 } ergibt
(map id) :: ([a0 ] → [a0 ]).
(foldr (:) [])::[c] → [c]
Grundlagen der Programmierung 2 (Typen-A)
– 22/34 –
foldr :: (a -> b -> b) -> b -> [a] -> b
(:) :: a -> [a] -> [a]
umbenannt: c -> [c] -> [c]
umbenannt: [d]
([]) :: [a]
→ [a] → [b]
G
∅;
∅;
{a 7→ a0 };
{a 7→ a0 , b 7→ a0 };
{a 7→ a0 , b 7→ a0 };
Grundlagen der Programmierung 2 (Typen-A)
Beispiel (foldr (:) []) :: ??
Berechne Typ von (map id)
map::
id::
Fehlerabbruch, wenn:
.
• x = t in E, x 6= t und x kommt in t vor.
– 23/34 –
Grundlagen der Programmierung 2 (Typen-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)
– 24/34 –
Beispiel. Linksfaltung: (foldl (:) [])?
Beispiel zu Typberechnung
Typ von map length
foldl :: (a -> b -> a) -> a -> [b] -> a
umbenannt: c -> [c] -> [c]
(:) :: a -> [a] -> [a]
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
map :: (a → b) → ([a] → [b]), length :: [a0 ] → Int
(map length) :: ?
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)}
.
.
{a = [a0 ], b = Int}
.
{b = Int}
∅
Somit: (map length) :: γ([a] → [b]) = [[a0 ]] → [Int]
(foldl (:) []) ist nicht typisierbar!
Grundlagen der Programmierung 2 (Typen-A)
– 25/34 –
Beispiele zu polymorpher Typberechnung
(:) :: 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}
(Typen der Konstanten.)
Kein Typ als Resultat, denn:
.
[Integer] = [Char] ist nicht lösbar.
Typ (1 : []) :: [Int]
Grundlagen der Programmierung 2 (Typen-A)
– 26/34 –
Beispiel zu Typfehler
Berechne Typ der Liste [1]:
1 : Int
Grundlagen der Programmierung 2 (Typen-A)
– 27/34 –
Grundlagen der Programmierung 2 (Typen-A)
– 28/34 –
Beispiel zu Typfehler im Interpreter
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)
– 29/34 –
Typisierung von Funktionen und Ausdrücken
•
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)
– 30/34 –
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,
• liefert allgemeinste (Milner-)Typen
• benutzt Unifikation (eine optimierte Variante)
•
•
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:
Es gibt keine dynamischen Typfehler,
wenn das Programm statisch korrekt getypt ist
– 31/34 –
Grundlagen der Programmierung 2 (Typen-A)
– 32/34 –
Typisierung und Reduktion
Beachte:
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]
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]
– 33/34 –
Grundlagen der Programmierung 2 (Typen-A)
– 34/34 –
Herunterladen