Programmieren in Haskell Das Haskell Typsystem

Werbung
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Programmieren in Haskell
Das Haskell Typsystem
Peter Steffen
Robert Giegerich
Universität Bielefeld
Technische Fakultät
22.01.2010
1
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Belauscht ...
Lisa Lista: “Ohne Typen keine korrekten Programme!”
Harry Hacker: “Ach was! Korrekte Programme brauchen keine Typen!”
Wer hat recht?
2
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Einfache Datentypen (vordefiniert)
Die einfachsten Datentypen:
Bool
(), genannt Void
Char
Int, Integer
Rational, Float, Double
Int -> Int, Int -> Musik -> Musik, etc.
In einer funktionalen Sprache sind auch Funktionen Daten “erster Klasse” –
sie können nicht nur auf Argumente angewandt werden, sondern auch in
Datenstrukturen verpackt, als Argumente übergeben, oder zu neuen
Funktionen zusamengesetzt werden, also Ergebnisse sein. Nur Vergleichen
(EQ) und Ausdrucken (Show) kann man sie nicht.
3
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Algebraische Datentypen
Algebraische Datentypen werden durch Konstruktoren gebildet. Eigentlich
stellen sie die Formeln dar, mit denen dann gerechnet wird.
Musik (mit Konstruktoren Instr, Tempo, Note, Pause, *, +)
Bool (mit Konstruktoren True, False)
Ordering (mit Konstruktoren LT, EQ, GT)
Von diesen Beispielen ist nur Musik ein rekursiver Datentyp, der beliebig
viele (und aus viele Teilen zusammengesetzte) Werte enthält.
4
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Monomorphe Listen
Listen mit Elementen vom Typ Int:
data Intlist = IntNil | IntCons Int IntList
Einwände:
Das ist wenig nützlich – für jeden weiteren Element-Datentyp muss
man einen neuen Listentyp einführen. (In manchen
Programmiersprachen ist das tatsächlich so!)
Viele Operationen mit Listen hängen nicht vom Elementtyp ab – die
möchte man nur einmal programmieren!
5
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Parametrischer Typ-Polymorphismus
Das Haskell-Typkonzept ist so stark, dass Typ-korrrekte Proramme oft
auch schon korrekt sind. Das liegt am parametrischen
Typ-Polymorphismus.
Typen werden dadurch flexibler, aber weiter gilt der alte Grundsatz:
Ein typ-korrektes Programm erzeugt zur Laufzeit keine Typfehler.
oder auch
Well-typed programs don’t go wrong.
6
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Parametrischer Typ-Polymorphismus
Haskell erlaubt parametrisierte Typen:
data [a] = [] | a:[a] -- polymorphe Listen
-- beachte: alle Elemente haben den gleichen
Typ
data Tree a
= Leaf a | Br (Tree a) (Tree a)
data TTree a b = Leaf a | Br (Tree b a) [a] (Tree b a)
-- hier wechseln die Typen pro Ebene,
-- aber streng kontrolliert
f
g
7
:: [a] -> [a]
:: Tree a -> [a]
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Typdeklarationen
Typdeklarationen in Haskell sind optional. Sie dienen
der Dokumentation
der bewußten Einschränkung des Typs (enger als nötig), z.B.
reversi:: [Int] -> [Int]
reversi xs = reverse xs
wenn man möchte, dass reversi NUR auf Int-Listen angewandt werden
kann.
Auch ohne Typdeklarationen können alle Typen bestimmt und geprüft
werden.
8
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Typinferenz
Grundlegendes zur Typinferenz:
Jeder Ausdruck in einem Haskell-Programm hat einen allgemeinsten
Typ.
Dieser Typ kann automatisch bestimmt werden durch Typinferenz.
Beim Rechnen spielen die Typen keine Rolle (mehr).
Meistens stimmt der inferierte Typ mit dem deklarierten (sofern
vorhanden) überein.
Andernfalls gilt:
Der deklarierte Typ ist allgemeiner: FEHLER
Der deklarierte Typ ist spezieller: Der allgemeine Typ wird entsprechend
eingeschränkt.
DieTypen sind unvergleichbar: FEHLER
9
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Allgemeiner und spezieller ...
a
-- der allgemeinste Typ, den es gibt
-- (a ist beliebige Typvariable)
a -> b -- der allgemeinste Funktionstyp, den es gibt
Ein Typ ist spezieller als ein anderer, wenn er aus ihm durch (konsistentes)
Einsetzen für Typvariablen entsteht.
Hier eine Kette von Spezialisierungen:
a ==> b ==> (a -> b) => a -> (c -> d) ==> a -> b -> b
==> a -> [a] -> [a]
-- das ist der Typ von (:)
==> Tree b -> [Tree b] -> [Tree b] -- (:) in einer Anwendung
10
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Beispiele zur Typinferenz
Der Typ einer polymorphen Funktion spezialsiert sich in der Anwendung:
(:)
(’a’:"braham")
(: "xx")
(’x’ :)
("x":)
::
::
::
::
::
a -> [a] -> [a]
[Char]
Char -> [Char]
[Char] -> [Char]
[[Char]] -> [[Char]]
Hugs> :type (: ’a’)
ERROR - Type error in application
*** Expression : (: ’a’)
*** Term : (:)
*** Type : b -> [b] -> [b]
*** Does not match : a -> Char -> [b]
Scheitert die Typinferenz, liegt ein Fehler vor.
11
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Typinferenz für map
Wir gehen von der Definition aus:
map f [] = []
map f (x:xs) = f x : map f xs
ermittelter Typ
c -> d -> e
c -> d -> [b]
c -> [a] -> [b]
(f -> g) -> [a] -> [b]
(a -> g) -> [a] -> [b]
(a -> b) -> [a] -> [b]
-------
Begründung
map hat 2 Argumente
das Ergebnis ist eine Liste
das 2. Argument ist eine Liste
das 1.Argument ist eine Funktion,
die Argumente vom Typ a erhält,
und Ergebnisse vom Typ b liefert.
Den allgemeinsten Typ erhalten wir, weil wir nirgendwo eine unnötige
Annahme machen.
12
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Typinferenz eines Audrucks
Wir gehen von bereits bekannten Typen für Zahlen und vordefinierte
Funktionen aus und leiten eine Typaussage für einen Ausdruck ab:
map (1:)::h
map:: (a -> b) -> [a] -> [b]
(:):: c -> [c] -> [c]
1::Int
c == Int
(:):: Int -> [Int] -> [Int]
(1:):: [Int] -> [Int]
13
---------
der Ausdruck hat einen Typ
bereits bekannt
bereits bekannt
bereits bekannt
1 erstes Argument von (:)
Typ von (:) in diesem Kontext
(:) angewandt aufs erste Argument
bleibt eine Funktion des zweiten
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
[a] -> [b] == [Int] -> [Int] -- (1:) ist erstes Argument von map
a == Int, b == Int
-- Konsequenz des vorigen
map:: ([Int] -> [Int]) -> [[Int]] -> [[Int]] -- spezieller Typ von
-- map in diesem Ausdruck
map (1:):: [[Int]] -> ][Int]]-- map angewandt auf erstes Argument
-- bleibt eine Funktion des zweiten
h == [[Int]] -> [[Int]]
-- allgemeinster Typ für map (1:)
... und das stimmt überein mit dem, was diese Funktion berechnet:
Hugs> map (1:) [[5..9],[20..24],[2,4..8]]
[[1,5,6,7,8,9],[1,20,21,22,23,24],[1,2,4,6,8]]
14
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Typinferenz mit Typklassen
In Wirklichkeit ist es noch etwas komplizierter. Man weiss ja nur, dass 1
eine Zahl ist, also der Typklasse NUM angehört.
Hugs> :type 1
1 :: Num a => a
Hugs> :type (1:)
(1 :) :: Num a => [a] -> [a]
Hugs> :type map (1:)
map (1 :) :: Num a => [[a]] -> [[a]]
Hugs> :type map ((1::Integer):)
map (1:) :: [[Integer]] -> [[Integer]]
Hugs> :type map (1.5:)
map (1.5 :) :: Fractional a => [[a]] -> [[a]]
15
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Polymorphe Funktionen
Polymorphe Funktionen können auf alle Daten angewandt werden, zu
deren Typ sich ihr allgemeiner Typ spezialisieren lässt.
Vorteil: Kompakter Code, wenig Fehler.
16
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Was sagt ihr Typ über die Funktion?
Wir kennen:
id :: a -> a
ix x = x
Welche andere Funktionen gibt es noch von diesem recht allgemeinen Typ,
(abgesehen von umständlicheren Definitionen der gleichen Funktion)?
Keine.
17
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Was sagt ihr Typ über die Funktion?
Wir kennen:
id :: a -> a
ix x = x
Welche andere Funktionen gibt es noch von diesem recht allgemeinen Typ,
(abgesehen von umständlicheren Definitionen der gleichen Funktion)?
Keine.
17
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Was sagt ihr Typ über die Funktion? (2)
Welche Funktion hat den noch allgemeineren Typ
f :: a -> b
18
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Was sagt ihr Typ über die Funktion? (2)
Welche Funktion hat den noch allgemeineren Typ
f :: a -> b
Die überall undefinierte Funktion ...
f :: a -> b
f x = f x
... und sonst keine.
19
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Fluch der Allgemeinheit
Auf der ersten Blick scheint es paradox:
Die Funktionen mit den allgemeinsten Typen
f:: a -> b
f:: a -> a
sind die “nutzlosesten” Funktionen!
20
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Polymorphe Funktionen höherer Ordnung (1)
Was mag das sein:
sRoL:: solution ->
(a -> [a] -> solution -> solution)->
[a] -> solution
sRoL base extend = rec
where rec [] = base
rec (x:xs) = extend x xs (rec xs)
21
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Polymorphe Funktionen höherer Ordnung (1)
Was mag das sein:
sRoL:: solution ->
(a -> [a] -> solution -> solution)->
[a] -> solution
sRoL base extend = rec
where rec [] = base
rec (x:xs) = extend x xs (rec xs)
21
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
sRoL in Worten
sRoL ist eine Funktion, die als Arfumente einen Wert und eine Funktion
erwartet.
Angewandt auf diese, ergibt sie eine Funktion, die Listen verarbeitet
und dabei für die leere Liste den Wert einsetzt, und ansonsten den
Listenrest verarbeitet und sodann das Ergebnis abhängig vom Listenkopf
modifiziert.
Oder anders gesagt:
sRoL ist das SCHEMA der strukturellen Rekursion auf Listen.
22
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
sRoL in Worten
sRoL ist eine Funktion, die als Arfumente einen Wert und eine Funktion
erwartet.
Angewandt auf diese, ergibt sie eine Funktion, die Listen verarbeitet
und dabei für die leere Liste den Wert einsetzt, und ansonsten den
Listenrest verarbeitet und sodann das Ergebnis abhängig vom Listenkopf
modifiziert.
Oder anders gesagt:
sRoL ist das SCHEMA der strukturellen Rekursion auf Listen.
22
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
sRoL angewandt
isort = sRoL [] (\a _ s -> insert a s)
insert a = sRoL [a] (\b x s -> if a <= b then a:b:x else b:s)
Man muss sich klarmachen, dass s jeweils die (rekursiv errechnete) Lösung
der Aufgabe für x darstellt!
23
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
sRoL Varianten
sRoL bezieht sich explizit auf den Datentyp Liste.
Es werden ja die beiden Konstruktoren [] und (:) explizit benutzt.
Für andere Datentypen wie Musik oder Tree muss ein eigenes Schema
definiert werden.
Das wird noch eleganter bei der Wohlfundierten Rekursion!
24
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Schema der Wohlfundierten Rekursion
problem steht für den Datentyp, für den wir ein Problem mit
Wohlfundierter Rekursion lösen wollen.
daC:: (problem -> Bool) ->
(problem -> solution) ->
(problem -> [problem]) ->
([solution] -> solution) ->
problem -> solution
-----
easy
solve
divide
conquer
daC easy solve divide conquer = rec
where rec x = if easy x then solve x
else conquer [rec y| y <- divide x]
25
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Anwendung von daC
Implementierung des mergesort:
msort = daC (\x -> drop 1 x == [])
(\x -> x)
(\x -> let k = length x ‘div‘ 2 in [take k x, drop k x]
(\[s,t] -> merge s t)
Wo ist die Rekursion geblieben?
Sie wird vom Scheme daC eingebracht – das, worauf merge angewandt
wird, ist der msort von (take k x) und (drop k x).
26
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Anwendung von daC
Implementierung des mergesort:
msort = daC (\x -> drop 1 x == [])
(\x -> x)
(\x -> let k = length x ‘div‘ 2 in [take k x, drop k x]
(\[s,t] -> merge s t)
Wo ist die Rekursion geblieben?
Sie wird vom Scheme daC eingebracht – das, worauf merge angewandt
wird, ist der msort von (take k x) und (drop k x).
26
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Schlussfolgerungen
Funktionen höherer Ordnung sind ein machtvolles Hilfsmittel:
Sie erlauben, beliebige Schemata der Benutzung von funktionellen
Bauteilen zu definieren
Sie ersetzen das, was man in anderen Sprachen Kontrollstruktur
nennt.
Sie erlauben dem Programmierer, seine eigenen Kontrollstrukturen zu
definieren.
Dadurch lassen sich anwendungsbezogene Programmierstile
entwickeln – Beispiele sind
Pretty printing
Combinator Parsing
Algebraic Dynamic Programming
27
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Typklassen
Eine Typklasse ist eine Menge von Typen.
Sie ist charakterisiert durch eine Menge von Funktionen, die auf allen
Typen dieser Klasse und mit dem gleichen Namen definiert sind.
Diese Funktionen nennt man in diesem Zusammenhang gerne “Methoden”.
Vordefinierte Typklassen sind
Show – mit Methoden show, read, ...
Eq – mit Methoden ==, / =
Ord – mit Methoden <, >, >, 6 ...
Num – mit Methoden +, −, ∗, ...
und viele andere.
28
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Neue Typklassen
Neue Typklassen werden durch Klassendefinitionen eingeführt, die die
Typen der Operationen (hier gerne “Methoden” genannt) deklarieren.
Es können auch einige der Methoden als Default definiert werden, indem
sie sich gegenseitig benutzen.
Wenn ein Typ zur Instanz einer Klasse erklärt wird, müssen die Methoden
implementiert werden.
29
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Beispiel: Klasse Sequenzen
class Sequence s
empty
::
single
::
isEmpty
::
isSingle
::
vorndran
::
hintan
::
hd
::
tl
::
app
::
len
::
where
s a
a -> s a
s a -> Bool
s a -> Bool
a -> sa -> s a
sa -> a -> s a
s a -> a
s a -> s a
s a -> s a -> s a
s a -> Int
single a
= vorndran a empty
isSingle s = not(isEmpty s) && isEmpty (tl s)
len s
= if empty s then 0 else 1 + len (tl s)
30
Programmieren in Haskell
Einfache Datentypen
Parametrischer Typ-Polymorphismus
Polymorphe Funktionen
Typklassen
Instanziierung
Ein Datentyp kann zur Instanz einer Klasse erklärt werden:
instance Sequence [] where ...
instance Tree [] where ...
Hier müssen jeweils die Methoden implementiert werden.
Fehlt eine Implementierung, werden die Defaults aus der Klassendefinition
eingesetzt.
Diverse Instanziierungen von Sequence findet man im Skript.
31
Programmieren in Haskell
Herunterladen