Praktische Informatik 3 (WS 2010/11) - informatik.uni

Werbung
Personal
Praktische Informatik 3: Einführung in die Funktionale
Programmierung
Vorlesung vom 27.10.2010: Einführung
I
Vorlesung: Christoph Lüth <[email protected]>
Cartesium 2.046, Tel. 64223
I
Tutoren:
Diedrich Wolter <[email protected]>
Bernd Gersdorf <[email protected]>
Rene Wagner <[email protected]>
Christian Maeder <[email protected]>
Simon Ulbricht <[email protected]>
I
Fragestunde: Berthold Hoffmann
<[email protected]>
I
Website: www.informatik.uni-bremen.de/˜cxl/lehre/pi3.ws10.
Christoph Lüth & Dennis Walter
Universität Bremen
Wintersemester 2010/11
Rev. 1074
1 [20]
Termine
2 [20]
Übungsbetrieb
I
Vorlesung:
Mi 12 – 14, NW1 H 1 – H0020
I
Ausgabe der Übungsblätter über die Webseite Donnerstag Mittag
I
Tutorien:
Mo 10-12
Mo 16-18
Di 8-10
Di 10-12
Di 10-12
Di 12-14
I
Besprechung der Übungsblätter in den Tutorien
I
Bearbeitungszeit: eine Woche
I
Abgabe: elektronisch bis Montag um 10:00
MZH
MZH
MZH
MZH
MZH
MZH
5210
1380
1100
1380
1400
1450
Christian Maeder
Rene Wagner
Diedrich Wolter
Diedrich Wolter
Bernd Gersdorf
Simon Ulbricht
I
Fragestunde : Do 9 – 11 Berthold Hoffmann (Cartesium 2.048)
I
Elf Übungsblätter (voraussichtlich) plus 0. Übungsblatt
I
Anmeldung zu den Übungsgruppen über stud.ip
I
Übungsgruppen: max. drei Teilnehmer (nur in Ausnahmefällen vier)
3 [20]
Scheinkriterien
4 [20]
Spielregeln
I
I
I
I
Quellen angeben bei
I
Gruppenübergreifender Zusammenarbeit;
I
Internetrecherche, Literatur, etc.
Von n Übungsblättern werden n − 1 bewertet (geplant n = 11)
Insgesamt mind. 50% aller Punkte
Fachgespräch (Individualität der Leistung) am Ende
I
Erster Täuschungsversuch: Null Punkte
I
Zweiter Täuschungsversuch: Kein Schein.
I
Deadline verpaßt?
I
Triftiger Grund (z.B. Krankheit mehrerer Gruppenmitglieder)
I
Vorher ankündigen, sonst null Punkte.
5 [20]
Fahrplan
I
Warum funktionale Programmierung lernen?
Teil I: Funktionale Programmierung im Kleinen
I
Einführung
I
Funktionen und Datentypen
I
Rekursive Datentypen
I
Typvariablen und Polymorphie
I
Funktionen höherer Ordnung
I
Typinferenz
6 [20]
I
Denken in Algorithmen, nicht in Programmiersprachen
I
Abstraktion: Konzentration auf das Wesentliche
I
Wesentliche Elemente moderner Programmierung:
I
Datenabstraktion und Funktionale Abstraktion
I
Modularisierung
I
Typisierung und Spezifikation
I
Teil II: Funktionale Programmierung im Großen
I
Blick über den Tellerrand — Blick in die Zukunft
I
Teil III: Funktionale Programmierung im richtigen Leben
I
Studium 6= Programmierkurs — was kommt in 10 Jahren?
7 [20]
8 [20]
Warum Haskell?
Geschichtliches
I
Grundlagen 1920/30
I
I
Moderne Sprache
I
Standardisiert, mehrere Implementationen
I
Erste Programmiersprachen 1960
I
I
I
Interpreter: hugs
Compiler: ghc, nhc98
I
Rein funktional
I
I
FP (Backus); ML (Milner, Gordon); Hope (Burstall); Miranda (Turner)
Konsolidierung 1990
I
I
LISP (McCarthy), ISWIM (Landin)
Weitere Programmiersprachen 1970– 80
I
I
Kombinatorlogik und λ-Kalkül (Schönfinkel, Curry, Church)
CAML, Formale Semantik für Standard ML
Haskell als Standardsprache
Hype 2010
I
Scala, F#, Clojure
9 [20]
Referenzielle Transparenz
10 [20]
Definition von Funktionen
I
I
Programme als Funktionen
P : Eingabe → Ausgabe
I
Keine veränderlichen Variablen — kein versteckter Zustand
I
Rückgabewert hängt ausschließlich von Werten der Argumente ab,
nicht vom Aufrufkontext (referentielle Transparenz)
Zwei wesentliche Konstrukte:
I
Fallunterscheidung
I
Rekursion
Satz
Fallunterscheidung und Rekursion auf natürlichen Zahlen sind
turing-mächtig.
I
Beispiel:
(
I
fac(n) =
Alle Abhängigkeiten explizit
I
1
wenn n = 0
n · fac(n − 1) sonst
Funktion kann partiell sein.
11 [20]
Auswertung als Ausführungsbegriff
I
12 [20]
Programmieren mit Funktionen
Programme werden durch Gleichungen definiert:
I
f (x ) = E
I
I
Suchen nach Vorkommen von f , e.g. f (t)
t
f (t) wird durch E
ersetzt
x
I
Auswertung kann divergieren!
I
Operational (Ausführungsbegriff) vs. denotational (math. Modell)
I
f a c n = i f n == 0 then 1
e l s e n∗ f a c ( n−1)
Auswertung durch Anwenden der Gleichungen:
I
I
Nichtreduzierbare Ausdrücke sind Werte
I
Vorgebenene Basiswerte: Zahlen, Zeichen
I
Definierte Datentypen: Wahrheitswerte, Listen, . . .
Programme werden durch Gleichungen definiert:
Auswertung durch Reduktion von Ausdrücken:
fac(2)
if 2 == 0 then 1 else 2* fac(2-1)
2* fac(2- 1)
2* fac(1)
2* (if 1== 0 then 1 else 1* fac(1- 1))
2* 1* fac(1- 1)
2* 1* fac(0)
2* 1* (if 0== 0 then 1 else 0* fac(0- 1))
2* 1* 1
2
14 [20]
13 [20]
Nichtnumerische Werte
I
Typisierung
Rechnen mit Zeichenketten
I
r e p e a t n s == i f n == 0 then " "
e l s e s ++ r e p e a t ( n−1) s
I
Typen unterscheiden Arten von Ausdrücken:
repeat n s = . . .
Auswertung:
I
repeat 2 "hallo "
if 2 == 0 then "" else "hallo " ++ repeat (2-1) "hallo "
"hallo "++ repeat (2-1) "hallo "
"hallo "++ if 2-1 == 0 then ""
else "hallo "++ repeat ((2-1)-1) "hallo "
"hallo "++ ("hallo "++ repeat ((2-1)-1) "hallo ")
"hallo "++ ("hallo "++ if ((2-1)-1) == 0 then ""
else repeat (((2-1)-1)-1) "hallo ")
"hallo "++ ("hallo " ++ "")
"hallo hallo "
15 [20]
I
n
s
Zahl
Zeichenkette
Verschiedene Typen:
I
Basistypen (Zahlen, Zeichen)
I
strukturierte Typen (Listen, Tupel, etc)
Wozu Typen?
I
Typüberprüfung während Übersetzung erspart Laufzeitfehler
I
Programmsicherheit
16 [20]
Signaturen
I
Jede Funktion hat eine Signatur
fac
::
repeat
I
Übersicht: Typen in Haskell
Int → Int
::
Int → String → String
Typüberprüfung
I
fac nur auf Int anwendbar, Resultat ist Int
I
repeat nur auf Int und String anwendbar, Resultat ist String
I
Typ
Bezeichner
Beispiel
Ganze Zahlen
Int
0
94
Fließkomma
Double
3.0
3.141592
Zeichen
Char
’a’ ’x’
’\034’
Zeichenketten
String
"yuck"
"hi\nho\"\n"
Wahrheitswerte
Bool
True
False
Funktionen
a-> b
I
I
18 [20]
Zusammenfassung
I
Imperative Programmierung:
I
Zustandsübergang Σ → Σ, Lesen/Schreiben von Variablen
Kontrollstrukturen: Fallunterscheidung
Iteration
if . . . then . . . else
while . . .
I
I
Funktionale Programmierung:
I
Funktionen f : E → A
I
Kontrollstrukturen: Fallunterscheidung
Rekursion
’\n’
Später mehr. Viel mehr.
17 [20]
Imperativ vs. Funktional
-45
I
19 [20]
Programme sind Funktionen, definiert durch Gleichungen
I
Referentielle Transparenz
I
kein impliziter Zustand, keine veränderlichen Variablen
Ausführung durch Reduktion von Ausdrücken
Typisierung:
I
Basistypen: Zahlen, Zeichen(ketten), Wahrheitswerte
I
Strukturierte Typen: Listen, Tupel
I
Jede Funktion f hat eine Signatur f :: a-> b
20 [20]
Inhalt
I
Praktische Informatik 3: Einführung in die Funktionale
Programmierung
Vorlesung vom 03.11.2010: Funktionen und Datentypen
Auswertungsstrategien
I
I
Definition von Funktionen
I
Christoph Lüth & Dennis Walter
I
Universität Bremen
Wintersemester 2010/11
I
I
Aufzählungen
I
Produkte
Basisdatentypen:
Wahrheitswerte, numerische Typen, alphanumerische Typen
2 [36]
1 [36]
Fahrplan
I
Syntaktische Feinheiten
Definition von Datentypen
I
Rev. 1167
Striktheit
Auswertungsstrategien
Teil I: Funktionale Programmierung im Kleinen
i n c :: I n t → I n t
i n c x = x+ 1
d o u b l e :: I n t → I n t
double x = 2∗x
I
Einführung
I
Funktionen und Datentypen
I
Reduktion von inc (double (inc 3))
I
Rekursive Datentypen
I
I
Typvariablen und Polymorphie
I
Funktionen höherer Ordnung
I
Typinferenz
Von außen nach innen (outermost-first):
double (inc 3)+ 1
inc (double (inc 3))
2*(inc 3)+ 1
2*(3+ 1)+ 1
2*4+1
9
I
Von innen nach außen (innermost-first):
inc (double (inc 3))
inc (double (3+1))
inc (2*(3+ 1))
(2*(3+ 1))+ 1
2*4+1
9
I
Teil II: Funktionale Programmierung im Großen
I
Teil III: Funktionale Programmierung im richtigen Leben
4 [36]
3 [36]
Auswertungsstrategien und Konfluenz
Auswirkung der Auswertungsstrategie
I
Outermost-first entspricht call-by-need, verzögerte Auswertung.
Funktionale Programme sind für jede Auswertungsstrategie konfluent.
I
Innermost-first entspricht call-by-value, strikte Auswertung
Theorem (Normalform)
I
Beispiel:
Theorem (Konfluenz)
Terminierende funktionale Programme werten unter jeder
Auswertungsstragie jeden Ausdruck zum gleichen Wert aus (der
Normalform).
I
Auswertungsstrategie für nicht-terminierende Programme relevant.
I
Nicht-Termination nötig (Turing-Mächtigkeit)
div
::
Int → Int → Int
Ganzzahlige Division, undefiniert für div n 0
mult :: I n t → I n t → I n t
mult n m = i f n == 0 then 0
e l s e ( mult ( n− 1 ) m)+ m
I
Auswertung von mult 0 (div 1 0)
5 [36]
Striktheit
Wie definiere ich eine Funktion?
Generelle Form:
Definition (Striktheit)
Funktion f ist strikt
6 [36]
I
⇐⇒
Signatur:
Ergebnis ist undefiniert
sobald ein Argument undefiniert ist
I
Semantische Eigenschaft (nicht operational)
I
Standard ML, Java, C etc. sind strikt (nach Sprachdefinition)
I
Haskell ist nicht-strikt (nach Sprachdefinition)
max ::
I
Int → Int → Int
Definition
max x y = i f x < y then y e l s e x
I
I
I
Kopf, mit Parametern
I
Rumpf (evtl. länger, mehrere Zeilen)
I
Typisches Muster: Fallunterscheidung, dann rekursiver Aufruf
I
Was gehört zum Rumpf (Geltungsberereich)?
Meisten Implementationen nutzen verzögerte Auswertung
Fallunterscheidung ist immer nicht-strikt
7 [36]
8 [36]
Haskell-Syntax: Charakteristika
I
I
Geltungsbereich der Definition von f:
alles, was gegenüber f eingerückt ist.
I
Beispiel:
f x = h i e r f a e n g t s an
und h i e r g e h t s w e i t e r
immer w e i t e r
g y z = und h i e r f a e n g t was n e u e s an
Keine Klammern
Abseitsregel: Gültigkeitsbereich durch Einrückung
I
I
Wichtigstes Zeichen:
Funktionsapplikation: f a
I
I
Funktionsdefinition:
f x1 x2 . . . xn = E
Leichtgewichtig
I
I
Haskell-Syntax I: Die Abseitsregel
Keine Klammern
I
Gilt auch verschachtelt.
I
Kommentare sind passiv
Auch in anderen Sprachen (Python, Ruby)
9 [36]
Haskell-Syntax II: Kommentare
I
I
Haskell-Syntax III: Bedingte Definitionen
I
Pro Zeile: Ab −− bis Ende der Zeile
f x y = irgendwas
10 [36]
f x y = i f B1 then P e l s e
i f B2 then Q e l s e . . .
−− und hier der Kommentar!
. . . bedingte Gleichungen:
Über mehrere Zeilen: Anfang {-, Ende -}
f x y
| B1 = . . .
| B2 = . . .
{−
H i e r f ä n g t d e r Kommentar an
e r s t r e c k t s i c h über mehrere Z e i l e n
bis hier
f x y = irgendwas
I
Statt verschachtelter Fallunterscheidungen . . .
−}
I
Auswertung der Bedingungen von oben nach unten
I
Wenn keine Bedingung wahr ist: Laufzeitfehler! Deshalb:
Kann geschachtelt werden.
| otherwise =...
11 [36]
Haskell-Syntax IV: Lokale Definitionen
I
Datentypen als Modellierungskonstrukt
Programme manipulieren ein Modell (der Umwelt)
Lokale Definitionen mit where oder let:
f x y
| g = P y
| otherwise =
Q where
y =M
f x = N x
f x y =
let y = M
f x = N x
in
i f g then P y
else Q
I
f, y, . . . werden gleichzeitig definiert (Rekursion!)
I
Namen f, y und Parameter (x) überlagern andere
I
Es gilt die Abseitsregel
I
12 [36]
I
Funktionale Sicht:
I
Imperative Sicht:
I
Objektorientierte Sicht:
Deshalb: Auf gleiche Einrückung der lokalen Definition achten!
13 [36]
Datentypen, Funktionen und Beweise
I
Datentypen konstruieren Werte
I
Funktionen definieren Berechnungen
I
Berechnungen haben Eigenschaften
I
Dualität:
14 [36]
Typkonstruktoren
Datentypkonstruktor ←→ Definitionskonstrukt ←→ Beweiskonstrukt
15 [36]
I
Aufzählungen
I
Produkt
I
Rekursion
I
Funktionsraum
16 [36]
Aufzählungen
Aufzählung und Fallunterscheidung in Haskell
I
I
Aufzählungen: Menge von disjunkten Konstanten
data Days = Mon | Tue | Wed | Thu | F r i | S a t | Sun
Days = {Mon, Tue, Wed, Thu, Fri, Sat, Sun}
I
I
Mon 6= Tue, Mon 6= Wed, Tue 6= Thu, Wed 6= Sun . . .
I
I
Genau sieben unterschiedliche Konstanten
I
Funktion mit Wertebereich Days muss sieben Fälle unterscheiden
I
Beispiel: weekend : Days → Bool mit
weekend(d) =
Definition
Implizite Deklaration der Konstruktoren Mon :: Days als Konstanten
Großschreibung der Konstruktoren
Fallunterscheidung:
weekend :: Days → Bool
weekend d = case d o f
S a t → True
Sun → True
Mon → F a l s e
Tue → F a l s e
Wed → F a l s e
Thu → F a l s e
Fri → False


 true
d = Sat ∨ d = Sun
false d = Mon ∨ d = Tue ∨ d = Wed ∨


d = Thu ∨ d = Fri
weekend d =
case d o f
S a t → True
Sun → True
_
→ False
17 [36]
Fallunterscheidung in der Funktionsdefinition
I
Der einfachste Aufzählungstyp
I
Abkürzende Schreibweise (syntaktischer Zucker):
f c1 == e1
...
f cn == en
18 [36]
Einfachste Aufzählung: Wahrheitswerte
Bool = {True, False}
f x == case x of c1 → e1 ,
...
cn → en
−→
I
I
I
Damit:
Definition von Funktionen:
I
weekend :: Days → Bool
weekend S a t = True
weekend Sun = True
weekend _
= False
Genau zwei unterschiedliche Werte
Wertetabellen sind explizite Fallunterscheidungen
∧
true
false
true
true
false
true
true
false
false
false
false
false
∧
∧
∧
∧
true
false
true
false
=
=
=
=
true
false
false
false
19 [36]
Wahrheitswerte: Bool
I
20 [36]
Beispiel: Ausschließende Disjunktion
Vordefiniert als
I
data Bool = True | F a l s e
I
I
exOr :: Bool → Bool → Bool
exOr x y = ( x | | y ) && ( n o t ( x && y ) )
Vordefinierte Funktionen:
not
&&
||
::
::
::
Bool → Bool
Bool → Bool → Bool
Bool → Bool → Bool
−− Negation
−− Konjunktion
−− Disjunktion
I
Konjunktion definiert als
I
&&, || sind rechts nicht strikt
I
I
1 ==0 && div 1 0 ==0
Alternative 1: explizite Wertetabelle:
exOr
exOr
exOr
exOr
a && b = case a o f True → b
False → False
I
Mathematische Definiton:
False
True
False
True
False
False
True
True
=
=
=
=
False
True
True
False
Alternative 2: Fallunterscheidung auf ersten Argument
exOr True y = n o t y
exOr F a l s e y = y
False
if then else als syntaktischer Zucker:
I
if b then p else q −→ case b of True → p
False → q
Was ist am besten?
I
Effizienz, Lesbarkeit, Striktheit
21 [36]
Produkte
I
I
I
22 [36]
Produkte in Haskell
Konstruktoren können Argumente haben
Beispiel: Ein Datum besteht aus Tag, Monat, Jahr
Mathematisch: Produkt (Tupel)
I
data Date = Date I n t Month I n t
data Month = Jan | Feb | Mar | Apr | May | Jun
| J u l | Aug | Sep | Oct | Nov | Dec
Date = {Date (n, m, y) | n ∈ N, m ∈ Month, y ∈ N}
Month = {Jan, Feb, Mar, . . .}
I
I
Konstruktorargumente sind gebundene Variablen
I
year (D(n, m, y )) = y
Über Fallunterscheidung Zugriff auf Argumente der Konstruktoren:
day
:: Date → I n t
y e a r :: Date → I n t
day d = case d o f Date t m y → t
y e a r ( Date d m y ) = y
day (D(n, m, y )) = n
I
Beispielwerte:
today
= Date 5 Nov 2008
b l o om s d a y = Date 16 Jun 1904
Funktionsdefinition:
I
Konstruktoren mit Argumenten
Bei der Auswertung wird gebundene Variable durch konkretes Argument
ersetzt
23 [36]
24 [36]
Beispiel: Tag im Jahr
I
Fallunterscheidung und Produkte
Tag im Jahr: Tag im laufenden Monat plus Summe der Anzahl der
Tage der vorherigen Monate
I
y e a r D a y :: Date → I n t
y e a r D a y ( Date d m y ) = d + sumPrevMonths m where
sumPrevMonths :: Month → I n t
sumPrevMonths Jan = 0
sumPrevMonths m
= day sInM on th ( p r e v m) y +
sumPrevMonths ( p r e v m)
I
Dreieck, gegeben durch Kantenlänge
I
Kreis, gegeben durch Radius
I
Rechteck, gegeben durch zwei Kantenlängen
O = {Tri(a) | a ∈ R} ∪ {Circle(r ) | r ∈ R} ∪ {Rect(a, b) | a, b ∈ R}
data Obj = T r i Double | C i r c l e Double | R e c t Double Double
Tage im Monat benötigt Jahr als Argument (Schaltjahr!)
I
da y sI nMonth :: Month → I n t → I n t
p r e v :: Month → Month
I
Beispiel: geometrische Objekte
I
Berechung des Umfangs:
c i r c :: Obj → Double
c i r c ( T r i a ) = 3∗ a
c i r c ( C i r c l e r )= 2∗ p i ∗ r
c i r c ( R e c t a b )= 2∗ ( a+ b )
Schaltjahr: Gregorianischer Kalender
l e a p y e a r :: I n t → Bool
l e a p y e a r y = i f mod y 100 == 0 then mod y 400 == 0
e l s e mod y 4 == 0
25 [36]
Der Allgemeine Fall: Algebraische Datentypen
Beweis von Eigenschaften
Definition eines algebraischen Datentypen T:
data T = C1 t1,1 . . . t1,k1
...
| Cn tn,1 . . . tn,kn
I
26 [36]
I
Konstruktoren C1 , . . . , Cn sind disjunkt:
Ci x1 . . . xn = Cj y1 . . . ym −→ i = j
I
Konstruktoren sind injektiv:
C x1 . . . xn = C y1 . . . yn −→ xi = yi
I
Konstruktoren erzeugen den Datentyp:
∀x ∈ T . x = Ci y1 . . . ym
Eigenschaften von Programmen: Prädikate
I
Haskell-Ausdrücke vom Typ Bool
I
Allquantifizierte Aussagen:
wenn P(x ) Prädikat, dann ist ∀x .P(x ) auch ein Prädikat
I
Sonderfall Gleichungen s == t
I
Müssen nicht ausführbar sein
Diese Eigenschaften machen Fallunterscheidung möglich.
27 [36]
Wie beweisen?
28 [36]
Ein ganz einfaches Beispiel
I
Gleichungsumformung (equational reasoning)
I
Fallunterscheidungen
I
Induktion
I
Wichtig: formale Notation
addTwice :: I n t → I n t → I n t
addTwice x y = 2 ∗ ( x+ y )
Lemma: addTwice x (y + z) = addTwice (x + y ) z
addTwice x (y + z)
= 2 ∗ (x + (y + z))
— Def. addTwice
= 2 ∗ ((x + y ) + z)
— Assoziativität von +
= addTwice (x + y ) z — Def. addTwice
30 [36]
29 [36]
Fallunterscheidung
Das Rechnen mit Zahlen
max , min :: I n t → I n t → I n t
max x y = i f x < y then y e l s e x
min x y = i f x < y then x e l s e y
Lemma: max x y − min x
max x y − min x
• Fall: x < y
= y − min x y
= y −x
= |x − y |
• Fall: x ≥ y
= x − min x y
= x −y
= |x − y |
= |x − y |
Beschränkte Genauigkeit,
beliebige Genauigkeit,
←→
konstanter Aufwand
wachsender Aufwand
y = |x − y |
y
Haskell bietet die Auswahl:
— Def. max
— Def. min
— Wenn x < y , dann y − x = |x − y |
I
Int - ganze Zahlen als Maschinenworte (≥ 31 Bit)
I
Integer - beliebig große ganze Zahlen
— Def. max
— Def. min
— Wenn x ≥ y , dann x − y = |x − y |
I
Rational - beliebig genaue rationale Zahlen
I
Float, Double - Fließkommazahlen (reelle Zahlen)
31 [36]
32 [36]
Ganze Zahlen: Int und Integer
I
Fließkommazahlen: Double
I
Nützliche Funktionen (überladen, auch für Integer):
+, ∗ , ^ , −
abs
div , quot
mod , rem
::
::
::
::
Int →
Int →
Int →
Int →
Doppeltgenaue Fließkommazahlen (IEEE 754 und 854)
I
Int → Int
I n t −− Betrag
Int → Int
Int → Int
I
Logarithmen, Wurzel, Exponentation, π und e, trigonometrische
Funktionen
Konversion in ganze Zahlen:
I
fromIntegral :: Int, Integer-> Double
I
fromInteger :: Integer-> Double
Es gilt (div x y)*y + mod x y == x
I
Vergleich durch ==, /=, <=, <, . . .
I
round, truncate :: Double-> Int, Integer
I
Achtung: Unäres Minus
I
Überladungen mit Typannotation auflösen:
I
Unterschied zum Infix-Operator -
I
Im Zweifelsfall klammern: abs (-34)
round ( f r o m I n t 10)
I
::
Int
Rundungsfehler!
33 [36]
Alphanumerische Basisdatentypen: Char
34 [36]
Zusammenfassung
I
I
I
I
Nützliche Funktionen:
ord
chr
::
::
Striktheit
I
Notation für einzelne Zeichen: ’a’,. . .
I
I
Char → I n t
I n t → Char
I
::
::
::
::
Char →
Char →
Char →
Char →
Char
Char
Bool
Bool
I
I
I
I
Beweis durch Gleichungsumformung
Programmeigenschaften als Prädikate
Fallunterscheidung als Beweiskonstrukt
Wahrheitswerte Bool
Numerische Basisdatentypen:
I
I
Aufzählungen — Fallunterscheidung
Produkte
Funktionsdefinition und Beweis dual
I
toLower
toUpper
isDigit
isAlpha
Haskell ist spezifiziert als nicht-strikt
Datentypen und Funktionsdefinition dual
Int, Integer, Rational und Double
Zeichenketten: String
35 [36]
I
Alphanumerische Basisdatentypen: Char
I
Nächste Vorlesung: Rekursive Datentypen
36 [36]
Fahrplan
I
Praktische Informatik 3: Einführung in die Funktionale
Programmierung
Vorlesung vom 10.11.2010: Rekursive Datentypen
Christoph Lüth & Dennis Walter
Teil I: Funktionale Programmierung im Kleinen
I
Einführung
I
Funktionen und Datentypen
I
Rekursive Datentypen
I
Typvariablen und Polymorphie
I
Funktionen höherer Ordnung
I
Typinferenz
Universität Bremen
Wintersemester 2010/11
Rev. 1152
I
Teil II: Funktionale Programmierung im Großen
I
Teil III: Funktionale Programmierung im richtigen Leben
2 [23]
1 [23]
Inhalt
I
I
Der Allgemeine Fall: Algebraische Datentypen
Definition eines algebraischen Datentypen T:
data T = C1 t1,1 . . . t1,k1
...
| Cn tn,1 . . . tn,kn
Rekursive Datentypen
I
Formen der Rekursion
I
Rekursive Definition
I
Rekursive Datentypen in anderen Sprachen
I
Konstruktoren C1 , . . . , Cn sind disjunkt:
Ci x1 . . . xn = Cj y1 . . . ym −→ i = j
I
Konstruktoren sind injektiv:
C x1 . . . xn = C y1 . . . yn −→ xi = yi
I
Konstruktoren erzeugen den Datentyp:
∀x ∈ T . x = Ci y1 . . . ym
Induktiver Beweis
I
Schluss vom kleineren aufs größere
Diese Eigenschaften machen Fallunterscheidung möglich.
3 [23]
Rekursive Datentypen
4 [23]
Induktive Definitionen
I
I
Der definierte Typ T kann rechts benutzt werden.
I
Rekursive Datentypen sind unendlich
I
Entspricht induktiver Definition
I
Beispiel natürliche Zahlen: Peano-Axiome
I
0∈N
I
wenn n ∈ N, dann S n ∈ N
I
S injektiv und S n 6= 0
I
Induktionsprinzip: φ(0), φ(x ) −→ φ(S x ), dann ∀n ∈ N.φ(n)
Induktionsprinzip erlaubt Definition rekursiver Funktionen:
n+0 = n
n + S m = S(n + m)
5 [23]
Natürliche Zahlen in Haskell
6 [23]
Beispiel: Zeichenketten selbstgemacht
I
Direkte Übersetzung der Peano-Axiome
I
Der Datentyp:
I
entweder leer (das leere Wort )
data Nat = Z er o
| S Nat
I
oder ein Zeichen c und eine weitere Zeichenkette xs
I
I
Eine Zeichenkette ist
data M y S t r i n g = Empty
| Cons Char M y S t r i n g
Rekursive Funktionsdefinition:
add :: Nat → Nat → Nat
add n Z e ro = n
add n ( S m) = S ( add n m)
I
Lineare Rekursion
I
7 [23]
Genau ein rekursiver Aufruf
8 [23]
Rekursive Definition
Funktionen auf Zeichenketten
I
I
l e n :: M y S t r i n g → I n t
l e n Empty
= 0
l e n ( Cons c s t r ) = 1+ l e n s t r
Typisches Muster: Fallunterscheidung
I
Ein Fall pro Konstruktor
I
I
Länge:
c a t :: M y S t r i n g → M y S t r i n g → M y S t r i n g
c a t Empty t
= t
c a t ( Cons c s ) t = Cons c ( c a t s t )
Hier:
I
Leere Zeichenkette
I
Nichtleere Zeichenkette
Verkettung:
I
Umkehrung:
r e v :: M y S t r i n g → M y S t r i n g
r e v Empty
= Empty
r e v ( Cons c t ) = c a t ( r e v t ) ( Cons c Empty )
9 [23]
Baumartige Rekursion: Binäre Bäume
I
Wechselseitige Rekursion: Variadische Bäume
Datentyp:
data BTree = MtBTree
| BNode I n t BTree BTree
I
10 [23]
I
data VTree = MtVTree
| VNode S t r i n g VNodes
Funktion, bsp. Höhe:
data VNodes = MtVNodes
| VMore VTree VNodes
h e i g h t :: BTree → I n t
h e i g h t MtBTree = 0
h e i g h t ( BNode j l r ) = max ( h e i g h t l ) ( h e i g h t r )+ 1
I
Variable Anzahl Kinderknoten
I
Baumartige Rekursion
I
VNodes: Liste von Kinderbäumen
Doppelter rekursiver Aufruf
11 [23]
Wechselseitige Rekursion: Variadische Bäume
I
I
12 [23]
Rekursive Typen in anderen Sprachen
Hauptfunktion:
I
Standard ML: gleich
c o u n t :: VTree → I n t
c o u n t MtVTree = 0
c o u n t ( VNode _ ns ) = 1+ c o u n t _ n o d e s n s
I
Lisp: keine Typen, aber alles ist eine S-Expression
Hilfsfunktion:
I
data SExpr = Quote Atom | Cons SExpr SExpr
c o u n t _ n o d e s :: VNodes → I n t
c o u n t _ n o d e s MtVNodes = 0
c o u n t _ n o d e s ( VMore t n s )= c o u n t t+ c o u n t _ n o d e s n s
Python, Ruby:
I
Listen (Sequenzen) vordefiniert
I
Keine anderen Typen
13 [23]
Rekursive Typen in Java
I
I
14 [23]
Rekursive Typen in C
Nachbildung durch Klassen, z.B. für Listen:
I
class List {
public L i s t ( Object el , L i s t t l ) {
t h i s . elem= e l ;
t h i s . n e x t= t l ;
}
p u b l i c O b j e c t elem ;
public L i s t
next ;
I
C: Produkte, Aufzählungen, keine rekursiven Typen
Rekursion durch Zeiger
typedef s t r u c t l i s t _ t {
void
∗ elem ;
struct l i s t _ t ∗ next ;
} ∗list ;
I
Länge (iterativ):
int length () {
i n t i= 0 ;
f o r ( L i s t c u r= t h i s ; c u r 6= n u l l ; c u r= c u r . n e x t )
i ++ ;
return i ;
}
15 [23]
Konstruktoren nutzerimplementiert
l i s t c o n s ( v o i d ∗hd , l i s t t l )
{ list l ;
i f ( ( l= ( l i s t ) m a l l o c ( s i z e o f ( s t r u c t l i s t _ t ) ) )== NULL) {
p r i n t f ( " Out o f memory \n " ) ; e x i t ( −1);
}
l → elem= hd ; l → n e x t= t l ;
return l ;
}
16 [23]
Rekursive Definition, induktiver Beweis
I
Beweis durch vollständige Induktion
Definition durch Rekursion
I
Basisfall (leere Zeichenkette)
I
Rekursion (nicht-leere Zeichenkette)
Zu zeigen:
Für alle natürlichen Zahlen x gilt P(x ).
Beweis:
r e v :: M y S t r i n g → M y S t r i n g
r e v Empty
= Empty
r e v ( Cons c t ) = c a t ( r e v t ) ( Cons c Empty )
I
I
I
Induktionsbasis: P(0)
I
Induktionsschritt:
Reduktion der Eingabe (vom größeren aufs kleinere)
Beweis durch Induktion
I
I
Induktionsvoraussetzung P(x )
I
zu zeigen P(x + 1)
Schluß vom kleineren aufs größere
17 [23]
Beweis durch strukturelle Induktion (Zeichenketten)
18 [23]
Beweis durch strukturelle Induktion (Allgemein)
Zu zeigen:
Zu zeigen:
Für alle (endlichen) Zeichenketten xs gilt P(xs)
Für alle x in T gilt P(x )
Beweis:
Beweis:
I
Induktionsbasis: P()
I
Induktionsschritt:
I
I
Induktionsvoraussetzung P(xs)
I
zu zeigen P(x xs)
Für jeden Konstruktor Ci :
I
Voraussetzung: für alle ti,j gilt P(ti,j )
I
zu zeigen P(Ci ti,1 . . . ti,ki )
19 [23]
Beispielbeweise
20 [23]
Spezifikation und Korrektheit
I
Die ersten n Zeichen einer Zeichenkette (n ≥ 0)
takeN :: I n t → M y S t r i n g → M y S t r i n g
takeN n Empty = Empty
takeN n ( Cons c s ) = i f n == 0 then Empty
e l s e Cons c ( takeN ( n−1) s )
len s ≥ 0
len (cat s t) = len s + len t
len (rev s) = len s
(1)
I
Zeichenkette ohne die ersten n Zeichen (n ≥ 0)
dropN :: I n t → M y S t r i n g → M y S t r i n g
dropN n Empty
= Empty
dropN n ( Cons c s ) = i f n == 0 then Cons c s
e l s e dropN ( n− 1 ) s
(2)
(3)
I
Eigenschaften:
len (takeN n s) ≤ n
(4)
len (dropN n s) ≥ len s − n
(5)
cat (takeN n s) (dropN n s) = s
21 [23]
Zusammenfassung
I
Datentypen können rekursiv sein
I
Rekursive Datentypen sind unendlich (induktiv)
I
Funktionen werden rekursiv definiert
I
Formen der Rekursion: linear, baumartig, wechselseitig
I
Rekursive Definition ermöglicht induktiven Beweis
I
Nächste Woche: Abstraktion über Typen (Polymorphie)
23 [23]
(6)
22 [23]
Fahrplan
I
Praktische Informatik 3: Einführung in die Funktionale
Programmierung
Vorlesung vom 17.11.2010: Typvariablen und Polymorphie
Christoph Lüth & Dennis Walter
Teil I: Funktionale Programmierung im Kleinen
I
Einführung
I
Funktionen und Datentypen
I
Rekursive Datentypen
I
Typvariablen und Polymorphie
I
Funktionen höherer Ordnung
I
Typinferenz
Universität Bremen
Wintersemester 2010/11
Rev. 1180
I
Teil II: Funktionale Programmierung im Großen
I
Teil III: Funktionale Programmierung im richtigen Leben
2 [26]
1 [26]
Inhalt
Zeichenketten und Listen von Zahlen
I
Letzte VL: Eine Zeichenkette ist
I
I
I
Letzte Vorlesung: rekursive Datentypen
I
Diese Vorlesung:
data M y S t r i n g = Empty
| Cons Char M y S t r i n g
I
Eine Liste von Zahlen ist
I
I
I
entweder leer (das leere Wort )
oder ein Zeichen und eine weitere Zeichenkette
entweder leer
oder eine Zahl und eine weitere Liste
Abstraktion über Typen: Typvariablen und Polymorphie
data I n t L i s t = Empty
| Cons I n t I n t L i s t
I
Strukturell gleiche Definition
Zwei Instanzen einer allgemeineren Definition.
3 [26]
Typvariablen
I
4 [26]
Polymorphe Datentypen
I
Typvariablen abstrahieren über Typen
data L i s t α = Empty
| Cons α ( L i s t α)
I
α ist eine Typvariable
Typkorrekte Terme:
Typ
Empty
List α
Cons 57 Empty
List Int
Cons 7 (Cons 8 Empty)
List Int
Cons ’p’ (Cons ’i’ (Cons ’3’ Empty))
List Char
Cons True Empty
List Bool
I
α kann mit Char oder Int instantiiert werden
I
List α ist ein polymorpher Datentyp
I
Typvariable α wird bei Anwendung instantiiert
Nicht typ-korrekt:
Cons ’a’ (Cons 0 Empty)
I
Signatur der Konstruktoren
Cons True (Cons ’x’ Empty)
Empty :: L i s t α
Cons
:: α→ L i s t α→ L i s t α
wegen Signatur des Konstruktors:
I
Cons :: α→ L i s t α→ L i s t α
5 [26]
Polymorphe Funktionen
I
6 [26]
Polymorphe Funktionen
Verkettung von MyString:
I
c a t :: L i s t α → L i s t α → L i s t α
c a t Empty y s
= ys
c a t ( Cons x x s ) y s = Cons x ( c a t x s y s )
c a t :: M y S t r i n g → M y S t r i n g → M y S t r i n g
c a t Empty t
= t
c a t ( Cons c s ) t = Cons c ( c a t s t )
I
I
Polymorphie auch für Funktionen:
I
Verkettung von IntList:
Typvariable α wird bei Anwendung instantiiert:
c a t ( Cons 3 Empty ) ( Cons 5 ( Cons 57 Empty ) )
c a t ( Cons ’ p ’ ( Cons ’ i ’ Empty ) ) ( Cons ’ 3 ’ Empty )
c a t :: I n t L i s t → I n t L i s t → I n t L i s t
c a t Empty t
= t
c a t ( Cons c s ) t = Cons c ( c a t s t )
aber nicht
Gleiche Definition, unterschiedlicher Typ
c a t ( Cons True Empty ) ( Cons ’ a ’ ( Cons 0 Empty ) )
Zwei Instanzen einer allgemeineren Definition.
I
7 [26]
Typvariable: vergleichbar mit Funktionsparameter
8 [26]
Polymorphe Datentypen: Bäume
Tupel
Datentyp:
I
Mehr als eine Typvariable möglich
data BTree α = MtBTree
| BNode α ( BTree α) ( BTree α)
I
Beispiel: Tupel (kartesisches Produkt, Paare)
data P a i r α b = P a i r α b
Höhe des Baumes:
h e i g h t :: BTree α→ I n t
h e i g h t MtBTree = 0
h e i g h t ( BNode j l r ) = max ( h e i g h t l ) ( h e i g h t r )+ 1
I
Pair
I
Traversion — erzeugt Liste aus Baum:
Signatur des Konstruktors:
i n o r d e r :: BTree α→ L i s t α
i n o r d e r MtBTree = Empty
i n o r d e r ( BNode j l r ) =
c a t ( i n o r d e r l ) ( Cons j ( i n o r d e r r ) )
:: α→ β → P a i r α β
Beispielterm
Typ
Pair 4 ’x’
Pair Int Char
Pair (Cons True Empty) ’a’
Pair (List Bool) Char
Pair (3+ 4) (Cons ’a’ Empty)
Pair Int (List Char)
Cons (Pair 7 ’x’) Empty
List (Pair Int Char)
9 [26]
Vordefinierte Datentypen: Tupel und Listen
I
Eingebauter syntaktischer Zucker
I
Tupel sind das kartesische Produkt
Übersicht: vordefinierte Funktionen auf Listen I
( ++ )
::
(!!)
::
c o n c a t ::
l e n g t h ::
head , l a s t
tail , init
replicate
take
::
drop
::
s p l i t A t ::
r e v e r s e ::
zip
::
unzip
::
and , o r ::
sum
::
p r o d u c t ::
data (α , β ) = (α , β )
I
I
(a, b) = alle Kombinationen von Werten aus a und b
I
Auch n-Tupel: (a,b,c) etc.
Listen
data [ α ] = [ ] | α : [ α ]
I
10 [26]
Weitere Abkürzungen: [x]= x:[], [x,y] = x:y:[] etc.
[α] → [α] → [α]
−− Verketten
[α] → Int → α
−− n-tes Element selektieren
[ [ α ] ] → [α]
−− “flachklopfen”
[α] → Int
−− Länge
:: [ α ] → α
−− Erstes/letztes Element
:: [ α ] → [ α ]
−− Hinterer/vorderer Rest
:: I n t → α→ [ α ] −− Erzeuge n Kopien
Int → [α] → [α]
−− Erste n Elemente
Int → [α] → [α]
−− Rest nach n Elementen
I n t → [ α ] → ( [ α ] , [ α ] )−− Spaltet an Index n
[α] → [α]
−− Dreht Liste um
[ α ] → [ β ] → [ ( α , β ) ] −− Erzeugt Liste v. Paaren
[ ( α , β ) ] → ( [ α ] , [ β ] ) −− Spaltet Liste v. Paaren
[ Bool ] → Bool
−− Konjunktion/Disjunktion
[ Int ] → Int
−− Summe (überladen)
[ Int ] → Int
−− Produkt (überladen)
11 [26]
Zeichenketten: String
I
12 [26]
Variadische Bäume
String sind Listen von Zeichen:
I
type S t r i n g = [ Char ]
I
Alle vordefinierten Funktionen auf Listen verfügbar.
I
Syntaktischer Zucker zur Eingabe:
data VTree α = MtVTree
| VNode α [ VTree α ]
I
Anzahl Knoten zählen:
Beispiel:
c o u n t :: VTree α→ I n t
c o u n t MtVTree = 0
c o u n t ( VNode _ ns ) = 1+ c o u n t _ n o d e s n s
c n t :: Char → S t r i n g → I n t
cnt c [ ]
= 0
c n t c ( x : x s ) = i f ( c== x ) then 1+ c n t c x s
else cnt c xs
c o u n t _ n o d e s :: [ VTree α ] → I n t
count_nodes [ ]
= 0
c o u n t _ n o d e s ( t : t s ) = c o u n t t+ c o u n t _ n o d e s t s
[ ’ y ’ , ’ o ’ , ’ h ’ , ’ o ’ ] == " yoho "
I
Variable Anzahl Kinderknoten: Liste von Kinderknoten
13 [26]
Berechnungsmuster für Listen
I
I
Listenkomprehension
I
Primitiv rekursive Definitionen:
I
Eine Gleichung für leere Liste
I
Eine Gleichung für nicht-leere Liste, rekursiver Aufruf
14 [26]
toL :: S t r i n g → S t r i n g
toL s = [ t o L o w e r c | c ← s ]
I
Jedes Element der Eingabeliste
I
wird getestet
I
und gegebenfalls transformiert
Buchstaben herausfiltern:
l e t t e r s :: S t r i n g → S t r i n g
l e t t e r s s = [ c | c ← s , isAlpha c ]
Komprehensionsschema:
I
Ein einfaches Beispiel: Zeichenkette in Kleinbuchstaben wandeln
I
Kombination: alle Buchstaben kanonisch kleingeschrieben
toLL :: S t r i n g → S t r i n g
toLL s = [ t o L o w e r c | c ← s , i s A l p h a c ]
15 [26]
16 [26]
Listenkomprehension
I
Variadische Bäume II
Allgemeine Form:
I
[ E c | c ← L, test c ]
I
I
Ergebnis: E c für alle Werte c in L, so dass test c wahr ist
I
Typen: L :: [α], c :: α, test :: α→ Bool, E :: α→ β, Ergebnis [β]
count ’ :: VTree α→ I n t
count ’ MtVTree = 0
count ’ ( VNode _ t s ) =
1+ sum [ count ’ t | t ← t s ]
I
Auch mehrere Generatoren und Tests möglich:
Die Höhe:
h e i g h t ’ :: VTree α→ I n t
h e i g h t ’ MtVTree = 0
h e i g h t ’ ( VNode _ t s ) =
1+ maximum ( 0 : [ h e i g h t ’ t | t ← t s ] )
[ E c1 . . . cn | c1 ← L1 , t e s t 1 c1 ,
c2 ← L2 c1 , t e s t 2 c1 c2 , . . .]
I
Die Zähl-Funktion vereinfacht:
E vom Typ α1 → α2 . . . → β
17 [26]
Beispiel: Permutation von Listen
perms
I
I
I
::
Beispiel: Quicksort
[α] → [ [ α ] ]
Permutation der leeren Liste
Permutation von x:xs
x an allen Stellen in alle Permutationen von xs eingefügt.
perms [ ] = [ [ ] ] −−- Wichtig!
perms ( x : x s ) = [ ps ++ [ x ] ++ q s
| r s ← perms xs ,
( ps , q s ) ← s p l i t s r s ]
I
18 [26]
I
Zerlege Liste in Elemente kleiner, gleich und größer dem ersten,
I
sortiere Teilstücke,
I
konkateniere Ergebnisse.
qsort
Dabei splits: alle möglichen Aufspaltungen
s p l i t s :: [ α ] → [ ( [ α ] , [ α ] ) ]
splits []
= [( [] , [] )]
s p l i t s ( y : ys ) = ( [ ] , y : ys ) :
[ ( y : ps , qs ) | ( ps , q s ) ← s p l i t s y s ]
::
[α] → [α]
qsort [ ] = [ ]
qsort ( x : xs ) =
qsort smaller
smaller = [
equals = [
larger = [
++ x : e q u a l s
y | y ← xs
y | y ← xs
y | y ← xs
++ q s o r t l a r g e r where
, y < x ]
, y == x ]
, y > x ]
19 [26]
Überladung und Polymorphie
20 [26]
Typklassen in polymorphen Funktionen
I
Fehler: qsort nur für Datentypen mit Vergleichsfunktion
I
Überladung: Funktion f :: a→ b existiert für einige, aber nicht für alle
Typen
I
I
I
I
Beispiel:
I
Gleichheit: (==) :: a→ a→ Bool
I
Vergleich: (<) :: a→ a→ Bool
I
Anzeige: show :: a→ String
qsort, korrekte Signatur:
qsort
I
:: Ord α ⇒ [ α ] → [ α ]
Element einer Liste (vordefiniert):
elem :: Eq α ⇒ α→ [ α ] → Bool
elem e [ ]
= False
elem e ( x : x s ) = e == x | | elem e x s
Lösung: Typklassen
I
Typklasse Eq für (==)
I
Typklasse Ord für (<) (und andere Vergleiche)
I
Typklasse Show für show
I
Liste ordnen und anzeigen:
s h o w s o r t e d :: ( Eq α , Show α) ⇒ [ α ] → S t r i n g
s h o w s o r t e d x = show ( q s o r t x )
Auch Ad-hoc Polymorphie (im Ggs. zur parametrischen Polymorpie)
22 [26]
21 [26]
Polymorphie in anderen Programmiersprachen: Java
I
Polymorphie in Java: Methode auf alle Subklassen anwendbar
I
I
Polymorphie in anderen Programmiersprachen: Java
Typkorrekte Konkatenenation:
Manuelle Typkonversion nötig, fehleranfällig
v o i d c o n c a t ( A b s L i s t<T> o )
{
A b s L i s t<T> c u r= t h i s ;
w h i l e ( c u r . n e x t 6= n u l l ) c u r= c u r . n e x t ;
c u r . n e x t= o ;
}
Neu ab Java 1.5: Generics
I
Damit parametrische Polymorphie möglich
c l a s s A b s L i s t<T> {
p u b l i c A b s L i s t (T e l , A b s L i s t<T> t l ) {
t h i s . elem= e l ;
t h i s . n e x t= t l ;
}
p u b l i c T elem ;
p u b l i c A b s L i s t<T> n e x t ;
Nachteil: Benutzung umständlich, weil keine Typherleitung
A b s L i s t<I n t e g e r> l=
new A b s L i s t<I n t e g e r>( new I n t e g e r ( 1 ) ,
new A b s L i s t<I n t e g e r>( new I n t e g e r ( 2 ) , n u l l ) ) ;
23 [26]
24 [26]
Polymorphie in anderen Programmiersprachen: C
I
Zusammenfassung
“Polymorphie” in C: void *
struct l i s t {
void
∗ head ;
struct l i s t ∗ tail ;
}
I
Typvariablen und (parametrische) Polymorphie: Abstraktion über
Typen
I
Vordefinierte Typen: Listen [a] und Tupel (a,b)
I
Berechungsmuster über Listen: primitive Rekursion,
Listenkomprehension
I
Überladung durch Typklassen
I
Nächste Woche: Funktionen höherer Ordnung
Gegeben:
int x = 7;
s t r u c t l i s t s = { &x , NULL } ;
I
I
s.head hat Typ void *:
int y ;
y= ∗ ( i n t ∗ ) s . head ;
I
Nicht möglich: head direkt als Skalar (e.g. int)
I
C++: Templates
25 [26]
26 [26]
Fahrplan
I
Praktische Informatik 3: Einführung in die Funktionale
Programmierung
Vorlesung vom 24.11.2010: Funktionen Höherer Ordnung
Christoph Lüth & Dennis Walter
Teil I: Funktionale Programmierung im Kleinen
I
Einführung
I
Funktionen und Datentypen
I
Rekursive Datentypen
I
Typvariablen und Polymorphie
I
Funktionen höherer Ordnung
I
Typinferenz
Universität Bremen
Wintersemester 2010/11
Rev. 1204
I
Teil II: Funktionale Programmierung im Großen
I
Teil III: Funktionale Programmierung im richtigen Leben
2 [29]
1 [29]
Inhalt
I
Funktionen höherer Ordnung
I
Funktionen als gleichberechtigte Objekte
I
Funktionen als Argumente
I
Spezielle Funktionen: map, filter, fold und Freunde
I
foldr vs foldl
Funktionen als Werte
I
Argumente können auch Funktionen sein.
I
Beispiel: Funktion zweimal anwenden
t w i c e :: (α→ α) → α→ α
twice f x = f ( f x)
I
Auswertung wie vorher:
twice inc 3
5
7
twice (twice inc ) 3
I
Beispiel: Funktion n-mal hintereinander anwenden:
i t e r :: I n t → (α→ α) → α→ α
iter 0 f x = x
i t e r n f x | n > 0 = f ( i t e r ( n−1) f x )
| otherwise = x
I
Auswertung:
iter 3 inc
6
4 [29]
3 [29]
Funktionen Höherer Ordnung
Funktionen als Argumente: Funktionskomposition
I
Slogan
“Functions are first-class citizens.”
I
Funktionen sind gleichberechtigt: Werte wie alle anderen
I
Grundprinzip der funktionalen Programmierung
I
Reflektion
I
Funktionen als Argumente
Funktionskomposition (mathematisch)
(◦) :: (β → γ ) → (α→ β ) → α→ γ
( f ◦ g) x = f (g x)
I
I
Vordefiniert
I
Lies: f nach g
Funktionskomposition vorwärts:
(>.>) :: (α→ β ) → (β → γ ) → α→ γ
( f >.> g ) x = g ( f x )
I
Nicht vordefiniert!
6 [29]
5 [29]
Funktionen als Argumente: Funktionskomposition
η-Kontraktion
I
I
Vertauschen der Argumente (vordefiniert):
(>.>) :: (α→ β ) → (β → γ ) → α→ γ
(>.>) = f l i p (◦)
f l i p :: (α→ β → γ ) → β → α→ γ
flip f b a = f a b
I
Alternative Definition der Vorwärtskomposition: Punktfreie Notation
I
Da fehlt doch was?! Nein:
(>.>) = flip (◦) ≡
I
η-Kontraktion (η-Äquivalenz)
(>.>) f g a = flip (◦) f g a
Damit Funktionskomposition vorwärts:
(>.>) :: (α→ β ) → (β → γ ) → α→ γ
(>.>) f g x = f l i p (◦) f g x
I
Operatorennotation
I
7 [29]
I
Bedingung: E :: α→β, x :: α, E darf x nicht enthalten
λx→ E x ≡ E
I
Syntaktischer Spezialfall Funktionsdefinition:
f x =E x ≡ f =E
Warum? Extensionale Gleichheit von Funktionen
8 [29]
Funktionen als Argumente: map
I
I
Funktionen als Argumente: filter
Funktion auf alle Elemente anwenden: map
I
Elemente filtern: filter
I
Signatur:
Signatur:
filter
(α→ β ) → [ α ] → [ β ]
map ::
I
I
Definition
(α→ Bool ) → [ α ] → [ α ]
Definition
filter p []
= []
f i l t e r p ( x : xs )
| p x
= x : f i l t e r p xs
| otherwise = f i l t e r p xs
map f [ ]
= []
map f ( x : x s ) = f x : map f x s
I
::
Beispiel:
I
toL :: S t r i n g → S t r i n g
toL = map t o L o w e r
Beispiel:
l e t t e r s :: S t r i n g → S t r i n g
l e t t e r s = f i l t e r isAlpha
9 [29]
Beispiel: Primzahlen
I
I
I
I
Partielle Applikation
Sieb des Erathostenes
I
10 [29]
I
Für jede gefundene Primzahl p alle Vielfachen heraussieben
Dazu: filtern mit \n→ mod n p /=0!
Namenlose (anonyme) Funktion
Funktionskonstruktor rechtsassoziativ:
a → b→ c ≡ a→ (b→ c)
I
Primzahlen im Intervall [1.. n]:
s i e v e :: [ I n t e g e r ] → [ I n t e g e r ]
sieve []
= []
s i e v e ( p : ps ) =
p : s i e v e ( f i l t e r ( \n → mod n p /= 0 ) p s )
I
Funktionsanwendung ist linksassoziativ:
I
Inbesondere: f (a b) 6= ( f a) b
f a b ≡ ( f a) b
I
Partielle Anwendung von Funktionen:
I
p r i m e s :: I n t e g e r → [ I n t e g e r ]
primes n = s i e v e [ 2 . . n ]
I
I
NB: Mit 2 anfangen!
Listengenerator [n.. m]
Für f :: a→ b→ c, x :: a ist f x :: b→ c (closure)
Beispiele:
I
I
Inbesondere: (a → b)→ c 6= a→ (b→ c)
I
I
map toLower :: String→ String
(3 ==) :: Int→ Bool
concat ◦ map ( replicate 2) :: String→ String
12 [29]
11 [29]
Einfache Rekursion
I
Einfache Rekursion
I
Einfache Rekursion: gegeben durch
f []
= A
f (x:xs) = x ⊗ f xs
I
eine Gleichung für die leere Liste
I
eine Gleichung für die nicht-leere Liste
I
Beispiel: sum, concat, length, (+
+), . . .
I
Auswertung:
I
I
sum [4,7,3]
concat [A, B, C]
length [4, 5, 6]
Allgemeines Muster:
Parameter der Definition:
I
Startwert (für die leere Liste) A :: b
I
Rekursionsfunktion ⊗ :: a -> b-> b
Auswertung:
f [x1,..., xn] = x1 ⊗ x2 ⊗ . . . ⊗ xn ⊗ A
4+7+3+0
A ++ B ++ C++ []
1+ 1+ 1+ 0
I
Terminiert immer
I
Entspricht einfacher Iteration (while-Schleife)
13 [29]
Einfach Rekursion durch foldr
I
Beispiele: foldr
Einfache Rekursion
I
Basisfall: leere Liste
I
Rekursionsfall: Kombination aus Listenkopf und Rekursionswert
14 [29]
I
sum :: [ I n t ] → I n t
sum x s = f o l d r (+) 0 x s
I
I
Signatur
foldr
I
::
Summieren von Listenelementen.
Flachklopfen von Listen.
c o n c a t :: [ [ a ] ] → [ a ]
c o n c a t x s = f o l d r ( ++ )
(α→ β → β ) → β → [ α ] → β
Definition
I
foldr f e []
= e
f o l d r f e ( x : xs ) = f x ( f o l d r f e xs )
[ ] xs
Länge einer Liste
l e n g t h :: [ a ] → I n t
l e n g t h x s = f o l d r ( λx n → n+ 1 ) 0 x s
15 [29]
16 [29]
Noch ein Beispiel: rev
I
Einfache Rekursion durch foldl
Listen umdrehen:
r e v :: [ a ] → [ a ]
rev [ ]
= []
r e v ( x : x s ) = r e v x s ++ [ x ]
I
I
foldr faltet von rechts:
foldr ⊗ [x1 , ..., xn ] A = x1 ⊗ (x2 ⊗ (. . . (xn ⊗ A)))
I
Warum nicht andersherum?
foldl ⊗ [x1 , ..., xn ] A = (((A ⊗ x1 ) ⊗ x2 ) . . .) ⊗ xn
I
Definition von foldl:
Mit fold:
rev xs = f o l d r snoc
[ ] xs
f o l d l :: (α → β → α) → α → [ β ] → α
foldl f a [] = a
f o l d l f a ( x : xs ) = f o l d l f ( f a x ) xs
s n o c :: a → [ a ] → [ a ]
s n o c x x s = x s ++ [ x ]
I
Unbefriedigend: doppelte Rekursion
17 [29]
Beispiel: rev revisited
18 [29]
foldr vs. foldl
I
I
f []
= A
f (x:xs) = x ⊗ f xs
Listenumkehr ist falten von links:
rev ’ xs = f o l d l ( f l i p
(:))
[ ] xs
I
I
I
f = foldr ⊗ A entspricht
Kann nicht-strikt in xs sein, z.B. and, or
f = foldl ⊗ A entspricht
Nur noch eine Rekursion
f xs
= g A xs
g a []
= a
g a (x:xs) = g (a ⊗ x) xs
I
Endrekursiv (effizient), aber strikt in xs
19 [29]
foldl = foldr
20 [29]
Funktionen Höherer Ordnung: Java
I
Definition (Monoid)
I
(⊗, A) ist ein Monoid wenn
A⊗x=x
(Neutrales Element links)
x⊗A=x
(Neutrales Element rechts)
(x ⊗ y) ⊗ z = x ⊗ (y ⊗ z)
(Assoziativät)
interface Collection {
Object f o l d ( Object f ( Object a , C o l l e c t i o n c ) ,
Object a ) }
I
interface Collection {
Object f o l d ( Foldable f , Object a ) ; }
foldl ⊗ A xs = foldr ⊗ A xs
I
Beispiele: length, concat, sum
I
Gegenbeispiel: rev
Aber folgendes:
interface Foldable {
Object f ( Object a ) ; }
Theorem
Wenn (⊗, A) Monoid, dann für alle A, xs
I
Java: keine direkte Syntax für Funktionen höherer Ordnung
Folgendes ist nicht möglich:
Vergleiche Iterator aus Collections Framework (Java SE 6):
p u b l i c i n t e r f a c e I t e r a t o r<E>
boolean hasNext ( ) ;
E next ( ) ; }
21 [29]
Funktionen Höherer Ordnung: C
I
Funktionen Höherer Ordnung: C
Implementierung von filter:
Implizit vorhanden: Funktionen = Zeiger auf Funktionen
l i s t f i l t e r ( i n t f ( void ∗x ) , l i s t
{ i f ( l == NULL) {
r e t u r n NULL ;
}
else {
list r;
r= f i l t e r ( f , l → n e x t ) ;
i f ( f ( l → elem ) ) {
l → n e x t= r ;
return l ;
}
else {
free ( l );
return r ;
} }
}
typedef s t r u c t l i s t _ t {
void
∗ elem ;
struct l i s t _ t ∗ next ;
} ∗list ;
list
f i l t e r ( i n t f ( void ∗x ) , l i s t
l );
I
Keine direkte Syntax (e.g. namenlose Funktionen)
I
Typsystem zu schwach (keine Polymorphie)
I
22 [29]
Benutzung: signal (C-Standard 7.14.1)
#i n c l u d e <s i g n a l . h>
void ( ∗ s i g n a l ( i n t sig , void ( ∗ func ) ( i n t ) ) ) ( i n t ) ;
23 [29]
l)
24 [29]
Übersicht: vordefinierte Funktionen auf Listen II
Allgemeine Rekursion
map
:: (α→ β ) → [ α ] → [ β ]
−− Auf alle anwenden
f i l t e r :: (α→ Bool ) → [ α ] → [ α ] −− Elemente filtern
foldr
:: (α→ β → β ) → β → [ α ] → β −− Falten v. rechts
foldl
:: (β → α→ β ) → β → [ α ] → β −− Falten v. links
t a k e W h i l e :: (α→ Bool ) → [ α ] → [ α ]
d r o p W h i l e :: (α→ Bool ) → [ α ] → [ α ]
−− takeWhile ist längster Prefix so dass p gilt, dropWhile der Rest
any
:: (α → Bool ) → [ α ] → Bool −− p gilt mind. einmal
all
:: (α → Bool ) → [ α ] → Bool −− p gilt für alle
elem :: ( Eq α) ⇒ α → [ α ] → Bool −− Ist enthalten?
z i p W i t h :: (α → β → γ ) → [ α ] → [ β ] → [ γ ]
−− verallgemeinertes zip
I
Einfache Rekursion ist Spezialfall der allgemeinen Rekursion
I
Allgemeine Rekursion:
I
Rekursion über mehrere Argumente
I
Rekursion über andere Datenstruktur
I
Andere Zerlegung als Kopf und Rest
25 [29]
Beispiele für allgemeine Rekursion: Sortieren
26 [29]
Beispiel für allgemeine Rekursion: Mergesort
I
I
m s o r t :: Ord α⇒ [ α ] → [ α ]
msort xs
| length xs ≤ 1 = xs
| o t h e r w i s e = merge ( m s o r t f ) ( m s o r t b ) where
( f , b ) = s p l i t A t ( ( l e n g t h xs ) ‘ div ‘ 2) xs
Quicksort:
I
zerlege Liste in Elemente kleiner, gleich und größer dem ersten,
I
sortiere Teilstücke, konkateniere Ergebnisse
Hauptfunktion:
I
I
Mergesort:
I
teile Liste in der Hälfte,
I
sortiere Teilstücke, füge ordnungserhaltend zusammen.
I
merge :: Ord α⇒ [ α ] → [ α ] → [ α ]
merge [ ] x = x
merge y [ ] = y
merge ( x : x s ) ( y : y s )
| x≤ y
= x : ( merge x s ( y : y s ) )
| o t h e r w i s e = y : ( merge ( x : x s ) y s )
27 [29]
Zusammenfassung
I
I
Funktionen höherer Ordnung
I
Funktionen als gleichberechtigte Objekte und Argumente
I
Partielle Applikation, η-Kontraktion, namenlose Funktionen
I
Spezielle Funktionen höherer Ordnung: map, filter, fold und Freunde
Formen der Rekursion:
I
Einfache und allgemeine Rekursion
I
Einfache Rekursion entspricht foldr
splitAt :: Int→ [α]→ ([ α], [α]) spaltet Liste auf
Hilfsfunktion: ordnungserhaltendes Zusammenfügen
29 [29]
28 [29]
Fahrplan
Praktische Informatik 3: Einführung in die Funktionale
Programmierung
Vorlesung vom 08.12.2010: Abstrakte Datentypen
I
Teil I: Funktionale Programmierung im Kleinen
I
Teil II: Funktionale Programmierung im Großen
I
Abstrakte Datentypen
I
Signaturen und Eigenschaften
I
Aktionen und Zustände
Christoph Lüth & Dennis Walter
Universität Bremen
Wintersemester 2010/11
I
Rev. 1258
2 [31]
1 [31]
Inhalt
Einfache Bäume
I
I
Teil III: Funktionale Programmierung im richtigen Leben
data Tree α = N u l l
| Node ( Tree α) α ( Tree α)
Abstrakte Datentypen
I
Allgemeine Einführung
I
Realisierung in Haskell
I
Beispiele
Schon bekannt: Bäume
I
Dazu Test auf Enthaltensein:
member ’ :: Eq α⇒ α→ Tree α→ Bool
member ’ _ N u l l = F a l s e
member ’ b ( Node l a r ) =
a == b | | member ’ b l | | member ’ b r
I
I
Problem: Suche aufwändig (Backtracking)
Besser: Baum geordnet
I
Noch besser: Baum balanciert
3 [31]
Geordnete Bäume
I
I
4 [31]
Geordnete Bäume
Voraussetzung:
I
Ordnung auf a (Ord a)
I
Es soll für alle Bäume gelten:
∀ x t . t= Node l a r −→ (member x l −→ x < a) ∧
(member x r −→ a < x)
I
Beispiel für eine Datentyp-Invariante
I
Ordnungserhaltendes Einfügen
i n s e r t :: Ord α⇒ α→ Tree α→ Tree α
i n s e r t a N u l l = Node N u l l a N u l l
i n s e r t b ( Node l a r )
| b < a = Node ( i n s e r t b l ) a r
| b == a = Node l a r
| b > a = Node l a ( i n s e r t b r )
Test auf Enthaltensein vereinfacht:
member :: Ord α⇒ α→ Tree α→ Bool
member _ N u l l = F a l s e
member b ( Node l a r )
| b < a = member b l
| a == b = True
| b > a = member b r
I
Problem: Erzeugung ungeordneter Bäume möglich.
I
Lösung: Verstecken der Konstrukturen.
I
Warum? E.g. Implementation von geordneten Mengen
5 [31]
Geordnete Bäume als abstrakter Datentyp
6 [31]
Abstrakte Datentypen
I
Es gibt einen Typ Tree a
Definition (ADT)
I
Es gibt Operationen
Ein abstrakter Datentyp (ADT) besteht aus einem (oder mehreren)
Typen und Operationen auf diesem.
I
empty :: Ord α⇒Tree α
I
I
I
Werte des Typen können nur über die bereitgestellten Operationen
erzeugt werden.
I
Eigenschaften von Werten des Typen (insb. ihre innere Struktur)
können nur über die bereitgestellten Operationen beobachtet werden.
Nicht Null :: Tree a, sonst Konstruktor sichtbar
insert :: Ord α⇒α→ Tree α→ Tree α
I
member :: Ord α⇒α→ Tree α→ Bool
I
. . . und keine weiteren!
Zur Implementation von ADTs in einer Programmiersprache:
Möglichkeit der Kapselung durch
I
Beispiel für einen abstrakten Datentypen
I
Datentyp-Invarianten können außerhalb des definierenden Moduls nicht
verletzt werden
7 [31]
I
Module
I
Objekte
8 [31]
ADTs in Haskell: Module
Beispiel: Exportliste für Bäume
I
Einschränkung der Sichtbarkeit durch Verkapselung
I
Modul: Kleinste verkapselbare Einheit
I
Ein Modul umfaßt:
I
I
Definitionen von Typen, Funktionen, Klassen
I
Deklaration der nach außen sichtbaren Definitionen
I
I
Syntax:
module Name (sichtbare Bezeichner) where Rumpf
I
I
sichtbare Bezeichner können leer sein
Gleichzeitig: Modul =
ˆ Übersetzungseinheit (getrennte Übersetzung)
Export als abstrakter Datentyp
module OrdTree (Tree, insert , member, empty) where
I
Typ Tree extern sichtbar
I
Konstruktoren versteckt
Export als konkreter Datentyp
module OrdTree (Tree(..), insert , member, empty) where
I
Konstruktoren von Tree sind extern sichtbar
I
Pattern Matching ist möglich
I
Erzeugung auch von ungeordneten Bäumen möglich
10 [31]
9 [31]
Benutzung von ADTs
I
I
Importe in Haskell
Operationen und Typen müssen bekannt gemacht werden (Import)
I
Schlüsselwort: import Name [hiding] (Bezeichner)
I
Bezeichner geben an, was importiert werden soll:
Möglichkeiten des Imports:
I
I
I
I
Ohne Bezeichner wird alles importiert
I
Mit hiding werden Bezeichner nicht importiert
I
Alle Importe stehen immer am Anfang des Moduls
Alles importieren
Nur bestimmte Operationen und Typen importieren
I
Bestimmte Typen und Operationen nicht importieren
Qualifizierter Import zur Vermeidung von Namenskollisionen
I
import qualified Name as OtherName
I
Z. B. import qualified Data.Map as M
11 [31]
Beispiel: Importe von Bäumen
12 [31]
Baumtraversion als Funktion höherer Ordnung
Import(e)
Bekannte Bezeichner
import OrdTree
Tree, insert, member,
Tree, empty
import OrdTree(insert)
insert
import OrdTree hiding (member)
Tree, empty, insert
import OrdTree(empty)
empty,
import OrdTree hiding (empty)
Tree, insert, member
Nützlich: Traversion als generisches fold
I
Dadurch Iteration über den Baum möglich, ohne Struktur offenzulegen
f o l d T :: (α→ β → β → β ) → β → Tree α→ β
foldT f e Null = e
f o l d T f e ( Node l a r ) =
f a ( foldT f e l ) ( foldT f e r )
empty
import OrdTree(Tree, empty)
I
I
Damit externe Definition von Aufzählung möglich:
enum :: Ord α⇒ Tree α→ [ α ]
enum = f o l d T ( λx l 1 l 2 → l 1 ++ x : l 2 )
[]
13 [31]
Schnittstelle vs. Implementation
14 [31]
Endliche Mengen: Typsignaturen (1)
I
Abstrakter Datentyp für endliche Mengen (polymorph über
Elementtyp)
type S e t a
I
Gleiche Schnittstelle kann unterschiedliche Implementationen haben
I
Leere Menge:
empty ::
I
Beispiel: (endliche) Mengen
I
Einfügen in eine Menge:
insert
I
Set a
:: Ord a ⇒ a → S e t a → S e t a
Test auf Enthaltensein
member :: Ord a ⇒ a → S e t a → Bool
15 [31]
16 [31]
Endliche Mengen: Typsignaturen (2)
I
null
I
I
:: Ord a ⇒ S e t a → S e t a → S e t a
I
Schnittmenge
Extensionalität
s1 ==s2 ⇔ (∀x . member x s1 ⇔ member x s2)
Einfügen und Enthaltensein
i n s e r t x ( i n s e r t y s ) == i n s e r t y ( i n s e r t x s )
member x ( i n s e r t x s )
:: Ord a ⇒ S e t a → S e t a → S e t a
Umwandlung zu Listen
I
t o L i s t :: S e t a → [ a ]
f r o m L i s t :: Ord a ⇒ [ a ] → S e t a
I
Die leere Menge
empty n u l l
n o t ( empty ( i n s e r t x s ) )
Vereinigung
intersection
I
I
S e t a → Bool
::
union
I
Endliche Mengen: Eigenschaften
Test auf leere Menge
Schnittmenge
member x ( i n t e r s e c t i o n s 1 s 2 ) ==
member x s 1 && member x s 2
Mappen und Falten:
I
map :: ( Ord a , Ord b ) ⇒ ( a → b ) → S e t a → S e t b
f o l d :: ( a → b → b ) → b → S e t a → b
Vereinigung
member x ( u n i o n s 1 s 2 ) ==
member x s 1 | | member x s 2
17 [31]
Endliche Mengen: Implementierung
I
Für den Anwender von Data.Set irrelevant!
I
Wichtig aus Implementierungssicht: Effizienz
I
Verschiedene Möglichkeiten der Repräsentation
I
Sortierte Listen: type Set a = [a]
I
Funktionen: type Set a = a → Bool
I
In der Tat verwendet: Balancierte Bäume
18 [31]
Endliche Abbildungen
I
Eine Sichtweise: Ersatz für Hashtables in imperativen Sprachen.
Sehr nützlich!
I
Abstrakter Datentyp für endliche Abbildungen (polymorph über
Schlüssel- und Werttyp)
type Map a b
I
Leere Abbildung:
empty :: Map a b
I
Hinzufügen eines Schlüssel/Wert-Paars
insert
data Set a = Tip | Bin Int a (Set a) (Set a)
(Int gibt die Größe des Baumes an.)
I
:: Ord a ⇒ a → b → Map a b → Map a b
Test auf Enthaltensein
member :: Ord a ⇒ a → Map a b → Bool
19 [31]
Weitere Funktionen
I
Endl. Abbildungen: Anwendungsbeispiele
Test auf leere Abbildung
null
I
20 [31]
I
:: Map a b → Bool
l o o k u p :: Ord a ⇒ a → Map a b → Maybe b
( ! ) :: Ord a ⇒ Map a b → a → b
I
Löschen
I
Einfügen und Duplikatkonflikte lösen
delete
n L e f t :: Warehouse → S t r i n g → I n t → Bool
nLeft w art n =
case s ‘ Data . Map . l o o k u p ‘ w o f
Nothing → F a l s e
Just m → m ≥ n
:: Ord a ⇒ a → Map a b → Map a b
S t r i n g → I n t → Warehouse →
Warehouse
addArticle art n w =
Data . Map . i n s e r t W i t h (+) a r t w
i n s e r t W i t h :: Ord a ⇒
( b → b → b ) → a → b → Map a b → Map a b
I
Anzahl von Artikeln im Warenhaus
type Warehouse = Data . Map S t r i n g I n t
Nachschlagen eines Werts
addArticle
Mappen und Falten:
::
map :: ( b → c ) → Map a b → Map a c
f o l d :: ( b → c → c ) → c → Map a b → c
21 [31]
Weiterer Datentyp: Prioritätswarteschlangen
I
22 [31]
Implementierung mittels Heaps
Signatur von Prioritätswarteschlangen ähnlich Stacks und FIFO
Queues:
I
I
type P r i o r i t y Q u e u e k a
Operationen:
I
empty :: PriorityQueue k a
I
null :: Ord k⇒ PriorityQueue k a → Bool
I
insert :: Ord k⇒ k → a → PriorityQueue k a → PriorityQueue k a
I
minKeyValue :: Ord k⇒ PriorityQueue k a → (k, a)
I
deleteMin :: Ord k⇒ PriorityQueue k a → PriorityQueue k a
data Heap k a =Nil | Branch k a (Heap k a) (Heap k a)
heapProp :: Ord k ⇒ Heap k a → Bool
heapProp N i l = True
heapProp ( Branch k a l r ) =
k ≤ min ( minH k l ) ( minH k r )
&& heapProp l && heapProp r
where minH k N i l = k
minH _ ( Branch k a l r ) =
min k ( min ( minH k l ) ( minH k r ) )
k steht für Priorität, a ist eigentlicher Wert
I
Ein Heap ist eine baumartige Datenstruktur mit der Heap-Eigenschaft:
I
23 [31]
Wurzelelement jedes Teilbaums ist minimales Element des Teilbaums
24 [31]
Beispiel: Heap-Eigenschaft
Binäre Heaps
I
Ein vollständiger binärer Baum mit Heap-Eigenschaft
I
Kein geordneter Baum
I
depth
depth
depth
1 +
1
12
Vollständigkeit zusätzlich zur Heap-Eigenschaft
2
4
8
5
:: Ord k ⇒ Heap k a → I n t
Nil = 0
( Branch _ _ l r ) =
max ( d e p t h l ) ( d e p t h r )
completeAt n N i l = F a l s e
c o m p l e t e A t n ( Branch _ _ l
i f n > 0 then c o m p l e t e A t
completeAt
e l s e n o t ( n u l l l ) && n o t
10
r) =
( n − 1 ) l &&
( n − 1) r
( null r )
complete t = d < 3 | | completeAt ( d − 3) t
where d = d e p t h t
25 [31]
Beispiel: Vollständigkeit
I
I
26 [31]
Operationen
Mit Knoten 8: vollständig
Ohne 8: höhenbalanciert, aber nicht vollständig
I
Exportierte Operationen:
s i n g l e t o n :: Ord k ⇒ k → a → Heap k a
s i n g l e t o n k a = Branch k a N i l N i l
0
empty :: Heap k a
empty = N i l
3
5
1
2
4
7
i n s e r t :: Ord k ⇒ k → a → Heap k a → Heap k a
insert k a Nil = singleton k a
i n s e r t k a ( Branch k ’ a ’ l r )
| k < k ’ = Branch k a ( i n s e r t k ’ a ’ r ) l
| o t h e r w i s e = Branch k ’ a ’ ( i n s e r t k a r ) l
8
minKeyValue :: Ord k ⇒ Heap k a → ( k , a )
minKeyValue ( Branch k a _ _) = ( k , a )
6
27 [31]
Entfernen des minimalen Elements
I
Effizienz
Zum Entfernen: “Hochziehen” des jeweils kleineren Kindelements
d e l e t e M i n :: Ord k ⇒ Heap k a → Heap k a
deleteMin t =
case t o f
Branch _ _ N i l r → r
Branch _ _ l N i l → l
Branch k a ( [email protected] ( Branch l k l a _ _ ) )
( [email protected] ( Branch r k r a _ _ ) ) →
i f l k < r k then Branch l k l a ( d e l e t e M i n l ) r
e l s e Branch r k r a l ( d e l e t e M i n r )
I
28 [31]
I
Laufzeitverhalten: O(log(n)) für insert und deleteMin, O(1) für
minKeyValue, singleton und empty.
I
Pairing Heaps als Alternative zu Binären Heaps
I
Worst-case Laufzeit für deleteMin in O(n)
I
Aber: amortisierte Kosten in O(log(n)) und in der Praxis erstaunlich schnell
Bsp.: 106 zufällig priorisierte Elemente einfügen und entfernen
I
Haskell: Laufzeit ≈ 7s (im Vergleich: ≈ 12.7s für Binärheap)
I
OCaml: ≈ 3.3s
I
Java (Binärer Heap als Array): ≈ 3.6s
Beispiele siehe ../uebung/ueb06/trees.pdf
I
29 [31]
Zusammenfassung
I
I
Abstrakte Datentypen (ADTs):
I
Besteht aus Typen und Operationen darauf
I
Realisierung in Haskell durch Module
I
Beispieldatentypen: endliche Mengen und Abbildungen,
Prioritätswarteschlangen
I
Nächste Vorlesung: ADTs durch Eigenschaften spezifizieren
Vorlesung nächste Woche (15.12.2010) entfällt wegen Tag der Lehre!
Der Übungsbetrieb findet normal statt.
31 [31]
Tests auf 2GHz Intel Dual Core
30 [31]
Fahrplan
Praktische Informatik 3: Einführung in die Funktionale
Programmierung
Vorlesung vom 05.01.2011: Signaturen und Eigenschaften
I
Teil I: Funktionale Programmierung im Kleinen
I
Teil II: Funktionale Programmierung im Großen
I
Abstrakte Datentypen
I
Signaturen und Eigenschaften
I
Aktionen und Zustände
Christoph Lüth & Dennis Walter
Universität Bremen
Wintersemester 2010/11
I
Rev. 1312
Teil III: Funktionale Programmierung im richtigen Leben
2 [26]
1 [26]
Abstrakte Datentypen
Signaturen
I
Letzte Vorlesung: Abstrakte Datentypen
Definition (Signatur)
I
Typ plus Operationen
Die Signatur eines abstrakten Datentyps besteht aus den Typen, und der
Signatur der darüber definierten Funktionen.
I
I
In Haskell: Module
Heute: Signaturen und Eigenschaften
I
Keine direkte Repräsentation in Haskell
I
Signatur: Typ eines Moduls
4 [26]
3 [26]
Zur Erinnerung: Endliche Abbildungen
Endliche Abbildung: Signatur
I
I
Endliche Abbildung (FiniteMap)
I
Typen: die Abbildung S, Adressen a, Werte b
I
Operationen (Auszug)
I
leere Abbildung: S
I
Abbildung an einer Stelle schreiben: S → a → b → S
I
Abbildung an einer Stelle lesen: S → a * b (partiell)
Adressen und Werte sind Parameter
type Map α β
I
Leere Abbildung:
empty :: Map α β
I
An eine Stelle einen Wert schreiben:
insert
I
:: Map α β → α → β → Map α β
An einer Stelle einen Wert lesen:
lookup
:: Map α β → α → Maybe β
5 [26]
Signatur und Eigenschaften
6 [26]
Beschreibung von Eigenschaften
Definition (Axiome)
I
Axiome sind Prädikate über den Operationen der Signatur
Signatur genug, um ADT typkorrekt zu benutzen
I
I
I
Elementare Prädikate P :
Insbesondere Anwendbarkeit und Reihenfolge
Signatur nicht genug, um Bedeutung (Semantik) zu beschreiben:
I
Was wird gelesen?
I
Wie verhält sich die Abbildung?
I
7 [26]
I
Gleichheit s ==t
I
Ordnung s < t
I
Selbstdefinierte Prädikate
Zusammengesetzte Prädikate
I
Negation not p
I
Konjunktion p && q
I
Disjunktion p | | q
I
Implikation p =⇒ q
8 [26]
Beobachtbare und Abstrakte Typen
Axiome für Map
I
I
I
Beobachtbare Typen: interne Struktur bekannt
I
l o o k u p empty a == N o t h i n g
I
Vordefinierte Typen (Zahlen, Zeichen), algebraische Datentypen (Listen)
I
Viele Eigenschaften und Prädikate bekannt
I
Lesen an vorher geschriebener Stelle liefert geschriebenen Wert:
I
Lesen an anderer Stelle liefert alten Wert:
l o o k u p ( i n s e r t m a b ) a == J u s t b
Abstrakte Typen: interne Struktur unbekannt
I
Lesen aus leerer Abbildung undefiniert:
a1 /= a2 =⇒ l o o k u p ( i n s e r t m a1 b ) a2 ==
l o o k u p m a2
Wenig Eigenschaft bekannt, Gleichheit nur wenn definiert
I
Beispiel Map:
Schreiben an dieselbe Stelle überschreibt alten Wert:
i n s e r t (m a b1 ) a b2 == i n s e r t m a b2
I
beobachtbar: Adressen und Werte
I
abstrakt: Speicher
I
Schreiben über verschiedene Stellen kommutiert:
a1 /= a2 =⇒ i n s e r t ( i n s e r t m a1 b1 ) a2 b2 ==
i n s e r t ( i n s e r t m a2 b2 ) a1 b1
10 [26]
9 [26]
Axiome als Interface
I
Axiome müssen gelten
I
I
I
empty ::
nach außen das Verhalten
nach innen die Implementation
Signatur + Axiome = Spezifikation
Nutzer
Spezifikation
Implementation
St α
empty :: Qu α
Wert ein/auslesen:
Wert ein/auslesen:
push
top
pop
enq :: α→ Qu α→ Qu α
f i r s t :: Qu α→ α
deq :: Qu α→ Qu α
:: α→ St α→ St α
:: St α→ α
:: St α→ St α
Test auf Leer:
isEmpty
I
Implementation kann getestet werden
I
Axiome können (sollten?) bewiesen werden
Queues
Typ: Qu α
Initialwert:
Stacks
Typ: St α
Initialwert:
für alle Werte der freien Variablen zu True auswerten
Axiome spezifizieren:
I
I
Signatur und Semantik
::
Test auf Leer:
St α→ Bool
Last in first out (LIFO).
isEmpty
:: Qu α→ Bool
First in first out (FIFO)
Gleiche Signatur, unterscheidliche Semantik.
11 [26]
Eigenschaften von Stack
12 [26]
Eigenschaften von Queue
First in first out (FIFO):
Last in first out (LIFO):
f i r s t ( enq a empty ) == a
t o p ( push a s ) == a
n o t ( i s E m p t y q ) =⇒ f i r s t ( enq a q ) == f i r s t q
pop ( push a s ) == s
deq ( enq a empty ) == empty
i s E m p t y empty
n o t ( i s E m p t y q ) =⇒ deq ( enq a q ) = enq a ( deq q )
n o t ( i s E m p t y ( push a s ) )
i s E m p t y ( empty )
push a s /= empty
n o t ( i s E m p t y ( enq a q ) )
enq a q /= empty
13 [26]
Implementation von Stack: Liste
Implementation von Queue
Sehr einfach: ein Stack ist eine Liste
data S t a c k a= S t a c k [ a ]
14 [26]
I
Mit einer Liste?
d e r i v i n g ( Show , Eq )
I
empty =
Stack
Problem: am Ende anfügen oder abnehmen ist teuer.
[]
I
Deshalb zwei Listen:
push a ( S t a c k s ) = S t a c k ( a : s )
top
( Stack
pop
::
[])
= e r r o r " S t a c k : t o p on empty s t a c k "
Stack a → Stack a
15 [26]
I
Erste Liste: zu entnehmende Elemente
I
Zweite Liste: hinzugefügte Elemente rückwärts
I
Invariante: erste Liste leer gdw. Queue leer
16 [26]
Repräsentation von Queue
Operation
empty
enq 9
enq 4
enq 7
deq
enq 5
enq 3
deq
deq
deq
deq
deq
Resultat
9
4
7
5
3
error
Implementation
Queue
I
Repräsentation
([], [])
([9], [])
([9], [4])
([9], [7, 4])
([4, 7], [])
([4, 7], [5])
([4, 7], [3, 5])
([7], [3, 5])
([5, 3], [])
([3], [])
([], [])
([], [])
9
4 →9
7 →4 →9
7 →4
5 →7 →4
3 →5 →7 →4
3 →5 →7
3 →5
3
Datentyp:
data Qu α = Qu [ α ] [ α ]
I
Leere Schlange: alles leer
empty = Qu [ ]
I
[]
Invariante: erste Liste leer gdw. Queue leer
i s E m p t y (Qu x s _) = n u l l x s
I
Erstes Element steht vorne in erster Liste
f i r s t (Qu [ ] _) = e r r o r " Queue : f i r s t o f empty Q"
f i r s t (Qu ( x : x s ) _) = x
17 [26]
Implementation
I
18 [26]
Axiome als Eigenschaften
Bei enq und deq Invariante prüfen
I
Axiome können getestet oder bewiesen werden
I
Tests finden Fehler, Beweis zeigt Korrektheit
I
Arten von Tests:
enq x (Qu x s y s ) = c h e c k x s ( x : y s )
deq (Qu [ ] _ )
= e r r o r " Queue : deq o f empty Q"
deq (Qu (_ : x s ) y s ) = c h e c k x s y s
I
I
Prüfung der Invariante nach dem Einfügen und Entnehmen
I
Unit tests (JUnit, HUnit)
I
Black Box vs.White Box
I
Zufallsbasiertes Testen
check garantiert Invariante
c h e c k :: [ α ] → [ α ] → Qu α
c h e c k [ ] y s = Qu ( r e v e r s e y s )
c h e c k x s y s = Qu x s y s
[]
I
Funktionale Programme eignen sich sehr gut zum Testen
19 [26]
Zufallsbasiertes Testen in Haskell
I
Werkzeug: QuickCheck
I
Zufällige Werte einsetzen, Auswertung auf True prüfen
I
Polymorphe Variablen nicht testbar
I
Deshalb Typvariablen instantiieren
I
I
Typ muss genug Element haben (hier Int)
I
Durch Signatur Typinstanz erzwingen
20 [26]
Axiome mit QuickCheck testen
I
Für das Lesen:
prop_read_empty :: I n t → Bool
prop_read_empty a =
l o o k u p ( empty :: Map I n t I n t ) a == N o t h i n g
p r o p _ r e a d _ w r i t e :: Map I n t I n t → I n t → I n t → Bool
p r o p _ r e a d _ w r i t e s a v=
l o o k u p ( i n s e r t s a v ) a == J u s t v
Freie Variablen der Eigenschaft werden Parameter der Testfunktion
I
Hier: Eigenschaften direkt als Haskell-Prädikate
I
Es werden N Zufallswerte generiert und getestet (N = 100)
22 [26]
21 [26]
Axiome mit QuickCheck testen
I
Axiome mit QuickCheck testen
I
Bedingte Eigenschaft in quickCheck:
I
A =⇒B mit A, B Eigenschaften
I
Typ ist Property
I
Es werden solange Zufallswerte generiert, bis N die Vorbedingung
erfüllende gefunden und getestet wurden, andere werden ignoriert.
Schreiben:
p r o p _ w r i t e _ w r i t e :: Map I n t I n t → I n t → I n t → I n t → Bool
prop_write_write s a v w =
i n s e r t ( i n s e r t s a v ) a w == i n s e r t s a w
I
p r o p _ w r i t e _ o t h e r ::
Map I n t I n t → I n t → I n t → I n t → I n t → P r o p e r t y
prop_write_other s a v b w =
a /= b =⇒ i n s e r t ( i n s e r t s a v ) b w ==
i n s e r t ( i n s e r t s b w) a v
p r o p _ r e a d _ w r i t e _ o t h e r ::
Map I n t I n t → I n t → I n t → I n t → P r o p e r t y
p r o p _ r e a d _ w r i t e _ o t h e r s a v b=
a /= b =⇒ l o o k u p ( i n s e r t s a v ) b == l o o k u p s b
I
23 [26]
Schreiben an anderer Stelle:
Test benötigt Gleichheit auf Map a b
24 [26]
Zufallswerte selbst erzeugen
I
I
Zusammenfassung
Problem: Zufällige Werte von selbstdefinierten Datentypen
I
Signatur: Typ und Operationen eines ADT
I
Gleichverteiltheit nicht immer erwünscht (e.g. [a])
I
Axiome: über Typen formulierte Eigenschaften
I
Konstruktion nicht immer offensichtlich (e.g. Map)
I
Spezifikation = Signatur + Axiome
In QuickCheck:
I
I
I
Typklasse class Arbitrary a für Zufallswerte
Eigene Instanziierung kann Verteilung und Konstruktion berücksichtigen
E.g. Konstruktion einer Map:
I
I
Interface zwischen Implementierung und Nutzung
I
Testen zur Erhöhung der Konfidenz und zum Fehlerfinden
I
Beweisen der Korrektheit
QuickCheck:
I
Zufällige Länge, dann aus sovielen zufälligen Werten Map konstruieren
I
Freie Variablen der Eigenschaften werden Parameter der Testfunktion
I
Zufallswerte in Haskell?
I
=⇒ für bedingte Eigenschaften
25 [26]
26 [26]
Fahrplan
Praktische Informatik 3: Einführung in die Funktionale
Programmierung
Vorlesung vom 12.01.2011: Aktionen und Zustände
I
Teil I: Funktionale Programmierung im Kleinen
I
Teil II: Funktionale Programmierung im Großen
I
Abstrakte Datentypen
I
Signaturen und Eigenschaften
I
Aktionen und Zustände
Christoph Lüth & Dennis Walter
Universität Bremen
Wintersemester 2010/11
I
Rev. 1312
I
2 [26]
1 [26]
Inhalt
I
Teil III: Funktionale Programmierung im richtigen Leben
Ein- und Ausgabe in funktionalen Sprachen
Problem:
Ein/Ausgabe in funktionale Sprachen
Aktionen und der Datentyp IO.
Aktionen
I
readString :: . . . →String ??
Lösung:
Funktionen
Haskell
I
Funktionen mit Seiteneffekten
nicht referentiell transparent.
Wo ist das Problem?
Reine
I
I
Aktionen als Werte
I
Seiteneffekte am Typ erkennbar
I
Aktionen können nur mit
Aktionen komponiert werden
I
„einmal Aktion, immer Aktion“
Umwelt
I
Aktionen als Zustandstransformationen
3 [26]
Aktionen als abstrakter Datentyp
I
ADT mit Operationen Komposition und Lifting
I
Signatur:
4 [26]
Elementare Aktionen
I
Zeile von stdin lesen:
getLine
type IO α
I
(=)
:: IO α → (α→ IO β ) → IO β
return
:: α→ IO α
I
Zeichenkette auf stdout ausgeben:
putStr
I
::
S t r i n g → IO ( )
Zeichenkette mit Zeilenvorschub ausgeben:
putStrLn
Plus elementare Operationen (lesen, schreiben etc)
:: IO S t r i n g
::
S t r i n g → IO ( )
5 [26]
Einfache Beispiele
I
6 [26]
Noch ein Beispiel
I
Echo einfach
ohce :: IO ( )
ohce = g e t L i n e
= λ s → p u t S t r L n ( r e v e r s e s )
ohce
echo1 :: IO ( )
echo1 = g e t L i n e = p u t S t r L n
I
Echo mehrfach
echo :: IO ( )
echo = g e t L i n e = p u t S t r L n = λ_ → echo
I
I
Was passiert hier?
I
Verknüpfen von Aktionen mit =
I
Jede Aktion gibt Wert zurück
Umgekehrtes Echo:
Was passiert hier?
I
Reine Funktion reverse wird innerhalb von Aktion putStrLn genutzt
I
Folgeaktion ohce benötigt Wert der vorherigen Aktion nicht
I
Abkürzung: p q = p = λ_ →
7 [26]
q
8 [26]
Die do-Notation
Drittes Beispiel
I
I
Syntaktischer Zucker für IO:
echo =
getLine
= λ s → p u t S t r L n s
echo
I
I
⇐⇒
echo3 :: I n t → IO ( )
echo3 c n t = do
p u t S t r ( show c n t ++ " : " )
s ← getLine
i f s /= " " then do
p u t S t r L n $ show c n t ++ " : " ++ s
echo3 ( c n t+ 1 )
else return ()
echo =
do s ← g e t L i n e
putStrLn s
echo
Rechts sind =, implizit.
I
Es gilt die Abseitsregel.
I
Zählendes, endliches Echo
Einrückung der ersten Anweisung nach do bestimmt Abseits.
Was passiert hier?
I
Kombination aus Kontrollstrukturen und Aktionen
I
Aktionen als Werte
I
Geschachtelte do-Notation
9 [26]
Module in der Standardbücherei
10 [26]
Ein/Ausgabe mit Dateien
I
Im Prelude vordefiniert:
Dateien schreiben (überschreiben, anhängen):
Ein/Ausgabe, Fehlerbehandlung (Modul IO)
I
I
Zufallszahlen (Modul Random)
type F i l e P a t h =
writeFile
::
appendFile
::
I
Kommandozeile, Umgebungsvariablen (Modul System)
I
Zugriff auf das Dateisystem (Modul Directory)
I
Zeit (Modul Time)
I
I
Datei lesen (verzögert):
readFile
I
String
F i l e P a t h → S t r i n g → IO ( )
F i l e P a t h → S t r i n g → IO ( )
::
F i l e P a t h → IO S t r i n g
Mehr Operationen im Modul IO der Standardbücherei
I
Buffered/Unbuffered, Seeking, &c.
I
Operationen auf Handle
11 [26]
Beispiel: Zeichen, Wörter, Zeilen zählen (wc)
12 [26]
Beispiel: wc verbessert.
I
Effizienter: Dateiinhalt einmal traversieren
I n t → I n t → I n t → Bool → S t r i n g
→ ( Int , Int , Int )
cnt l w c _ [ ] = ( l , w, c )
cnt l w c bl ( x : xs )
| i s S p a c e x && n o t b l = c n t l ’ (w+1 ) ( c+1 ) True x s
| i s S p a c e x && b l
= c n t l ’ w ( c+1 ) True x s
| otherwise
= c n t l w ( c+1 ) F a l s e x s where
l ’ = i f x == ’ \n ’ then l+1 e l s e l
cnt
wc :: S t r i n g → IO ( )
wc f i l e =
do c o n t ← r e a d F i l e f i l e
p u t S t r L n $ f i l e ++ " : " ++
show ( l e n g t h ( l i n e s c o n t ) ,
l e n g t h ( words c o n t ) ,
length cont )
I
I
Nicht sehr effizient — Datei wird im Speicher gehalten.
::
Hauptprogramm:
wc :: S t r i n g → IO ( )
wc f i l e = do
cont ← r e a d F i l e f i l e
p u t S t r L n $ f i l e ++ " : " ++ show ( c n t 0 0 0 F a l s e c o n t )
I
Datei wird verzögert gelesen und dabei verbraucht.
13 [26]
Aktionen als Werte
I
Aktionen sind Werte wie alle anderen.
I
Dadurch Definition von Kontrollstrukturen möglich.
I
Endlosschleife:
Fehlerbehandlung
f o r e v e r :: IO α→ IO α
forever a = a forever a
I
I
Fehler werden durch IOError repräsentiert
I
Fehlerbehandlung durch Ausnahmen (ähnlich Java)
ioError
catch
Iteration (feste Anzahl):
f o r N :: I n t → IO α→ IO ( )
f o r N n a | n == 0
= return ()
| o t h e r w i s e = a f o r N ( n−1) a
I
14 [26]
I
:: I O E r r o r → IO α
−− "throw"
:: IO α→ ( I O E r r o r → IO α) → IO α
Fehlerbehandlung nur in Aktionen
Vordefinierte Kontrollstrukturen (Control.Monad):
I
when, mapM, forM, sequence, . . .
15 [26]
16 [26]
Fehler fangen und behandeln
So ein Zufall!
I
I
Fehlerbehandlung für wc:
randomRIO ::
wc2 :: S t r i n g → IO ( )
wc2 f i l e =
c a t c h ( wc f i l e )
( λe → p u t S t r L n $ " F e h l e r : " ++ show e )
I
IOError kann analysiert werden (siehe Modul IO)
I
read mit Ausnahme bei Fehler (statt Programmabbruch):
readIO
Zufallswerte:
I
I
(α , α) → IO α
Warum ist randomIO Aktion?
Beispiel: Aktionen zufällig oft ausführen
a t m o s t :: I n t → IO α→ IO [ α ]
a t m o s t most a =
do l ← randomRIO ( 1 , most )
mapM i d ( r e p l i c a t e l a )
I
:: Read a ⇒ S t r i n g → IO a
Zufälligen String erzeugen
ran domStr :: IO S t r i n g
ran domStr = a t m o s t 40 ( randomRIO ( ’ a ’ , ’ z ’ ) )
17 [26]
Ausführbare Programme
18 [26]
Funktionen mit Zustand
I
Eigenständiges Programm ist Aktionen
I
Hauptaktion: main in Modul Main
Theorem (Currying)
I
wc als eigenständiges Programm:
Folgende Typen sind isomorph:
A×B →C ∼
=A→B→C
module Main where
I
import System . E n v i r o n m e n t ( g e t A r g s )
import Char ( i s S p a c e )
In Haskell: folgende Funktionen sind invers:
curry
uncurry
::
::
( ( α , β ) → γ ) → α→ β → γ
(α→ β → γ ) → (α , β ) → γ
main = do
args ← getArgs
mapM wc2 a r g s
20 [26]
19 [26]
Funktionen mit Zustand
I
Idee: Seiteneffekt explizit machen
I
Funktion f : A → B mit Seiteneffekt in Zustand S:
In Haskell: Zustände explizit
I
type S t a t e Σ α = Σ→ (α , Σ)
f :A×S →B×S
∼
=
I
I
Datentyp: S → B × S
I
Komposition: Funktionskomposition und uncurry
Komposition zweier solcher Berechnungen:
comp :: S t a t e Σ α→ (α→ S t a t e Σ β ) → S t a t e Σ β
comp f g = u n c u r r y g ◦ f
f :A→S →B×S
I
Datentyp: Berechnung mit Seiteneffekt in Typ Σ:
Lifting:
l i f t :: α→ S t a t e Σ α
l i f t = curry id
21 [26]
Beispiel: Ein Zähler
I
22 [26]
Implizite vs. explizite Zustände
Datentyp:
type W i t h C o u n t e r α = S t a t e I n t α
I
I
I
t i c k :: W i t h C o u n t e r ( )
t i c k i = ( ( ) , i+1 )
I
I
Zähler auslesen:
r e a d :: W i t h C o u n t e r I n t
read i = ( i , i )
I
Nachteil: Zustand ist explizit
Zähler erhöhen:
Zähler zurücksetzen:
Kann dupliziert werden
Daher: Zustand implizit machen
I
Datentyp verkapseln
I
Signatur State , comp, lift , elementare Operationen
r e s e t :: W i t h C o u n t e r ( )
r e s e t i = ( ( ) , 0)
23 [26]
24 [26]
Aktionen als Zustandstransformationen
I
Idee: Aktionen sind Transformationen auf Systemzustand S
I
S beinhaltet
I
I
Speicher als Abbildung A * V (Adressen A, Werte V )
I
Zustand des Dateisystems
I
Zustand des Zufallsgenerators
Zusammenfassung
I
Ein/Ausgabe in Haskell durch Aktionen
I
Aktionen (Typ IO α) sind seiteneffektbehaftete Funktionen
I
Komposition von Aktionen durch
(=)
return
In Haskell: Typ RealWorld
I
I
I
do-Notation
I
Fehlerbehandlung durch Ausnahmen (IOError, catch).
I
“Virtueller” Typ, Zugriff nur über elementare Operationen
Entscheidend nur Reihenfolge der Aktionen
I
25 [26]
:: IO α→ (α→ IO β ) → IO β
:: α→ IO α
Verschiedene Funktionen der Standardbücherei:
I
Prelude: getLine, putStr, putStrLn, readFile , writeFile
I
Module: IO, Random
Aktionen sind implementiert als Zustandstransformationen
26 [26]
Fahrplan
Praktische Informatik 3: Einführung in die Funktionale
Programmierung
Vorlesung vom 19.01.2011: Effizienzaspekte
I
Teil II: Funktionale Programmierung im Großen
I
Teil III: Funktionale Programmierung im richtigen Leben
I
Effizient Funktional Programmieren
Universität Bremen
I
Fallstudie: Kombinatoren
Wintersemester 2010/11
I
Eine Einführung in Scala
I
Rückblich & Ausblick
I
Zeitbedarf: Endrekursion — while in Haskell
I
Platzbedarf: Speicherlecks
I
“Unendliche” Datenstrukturen
I
Verschiedene andere Performancefallen:
2 [33]
1 [33]
Inhalt
I
Teil I: Funktionale Programmierung im Kleinen
Christoph Lüth & Dennis Walter
Rev. –revision–
I
I
Effizienzaspekte
I
Überladene Funktionen, Listen
Erste Lösung: bessere Algorithmen
I
Zweite Lösung: Büchereien nutzen
I
Analyse der Auswertungsstrategie
I
. . . und des Speichermanagement
I
Der ewige Konflikt: Geschwindigkeit vs. Platz
I
Effizenzverbesserungen durch
“Usual Disclaimers Apply”:
I
Zur Verbesserung der Effizienz:
I
I
Endrekursion: Iteration in funktionalen Sprachen
I
Striktheit: Speicherlecks vermeiden (bei verzögerter Auswertung)
Vorteil: Effizienz muss nicht im Vordergrund stehen
3 [33]
Endrekursion
4 [33]
Beispiel: Fakultät
I
Definition (Endrekursion)
Eine Funktion ist endrekursiv, wenn
fac1 nicht endrekursiv:
f a c 1 :: I n t e g e r → I n t e g e r
f a c 1 n = i f n == 0 then 1 e l s e n ∗ f a c 1 ( n−1)
(i) es genau einen rekursiven Aufruf gibt,
(ii) der nicht innerhalb eines geschachtelten Ausdrucks steht.
I
I
D.h. darüber nur Fallunterscheidungen: case oder if
I
Entspricht goto oder while in imperativen Sprachen.
I
Wird in Sprung oder Schleife übersetzt.
I
Braucht keinen Platz auf dem Stack.
fac2 endrekursiv:
f a c 2 :: I n t e g e r → I n t e g e r
fac2 n
= f a c ’ n 1 where
f a c ’ :: I n t e g e r → I n t e g e r → I n t e g e r
f a c ’ n a c c = i f n == 0 then a c c
e l s e f a c ’ ( n−1) ( n∗ a c c )
I
fac1 verbraucht Stack, fac2 nicht.
6 [33]
5 [33]
Beispiel: Listen umdrehen
I
Überführung in Endrekursion
Liste umdrehen, nicht endrekursiv:
I
r e v ’ :: [ a ] → [ a ]
rev ’ [ ]
= []
r e v ’ ( x : x s ) = r e v ’ x s ++ [ x ]
I
I
I
Gegeben Funktion
f0 :S →T
f 0 x = if B x then H x
else φ (f 0 (K x )) (E x )
I
Hängt auch noch hinten an — O(n2 )!
Mit K : S → S, φ : T → T → T , E : S → T , H : S → T .
Liste umdrehen, endrekursiv und O(n):
I
Voraussetzung: φ assoziativ, e : T neutrales Element
r e v :: [ a ] → [ a ]
r e v x s = r e v 0 x s [ ] where
rev0 [ ]
ys = ys
rev0 ( x : xs ) ys = rev0 xs ( x : ys )
I
Dann ist endrekursive Form:
f :S→T
f x = g x e where
g x y = if B x then φ (H x ) y
else g (K x ) (φ (E x ) y )
Beispiel: last (rev [1..10000])
7 [33]
8 [33]
Beispiel
I
Beispiel
Länge einer Liste (nicht-endrekursiv)
I
l e n g t h ’ :: [ a ] → I n t
l e n g t h ’ x s = i f n u l l x s then 0
e l s e 1+ l e n g t h ’ ( t a i l x s )
I
I
Zuordnung der Variablen:
K (x ) 7→ tail x
E (x ) 7→ 1
φ(x , y ) 7→ x + y
Es gilt: φ(x , e) = x + 0 = x
B(x ) →
7
H(x ) →
7
e →
7
Damit endrekursive Variante:
l e n g t h :: [ a ] → I n t
l e n g t h x s = l e n x s 0 where
l e n x s y = i f n u l l x s then y −− was: y+ 0
e l s e l e n ( t a i l x s ) ( 1+ y )
null x
0
0
I
(0 neutrales Element)
Allgemeines Muster:
I
Monoid (φ, e): φ assoziativ, e neutrales Element.
I
Zusätzlicher Parameter akkumuliert Resultat.
10 [33]
9 [33]
Endrekursive Aktionen
I
Fortgeschrittene Endrekursion
Nicht endrekursiv:
I
g e t L i n e s ’ :: IO S t r i n g
g e t L i n e s ’ = do s t r ← g e t L i n e
i f n u l l s t r then r e t u r n " "
e l s e do r e s t ← g e t L i n e s ’
r e t u r n ( s t r ++ r e s t )
I
Akkumulation von Ergebniswerten durch closures
I
I
closure: partiell applizierte Funktion
Beispiel: die Klasse Show
I
Nur Methode show wäre zu langsam (O(n2 )):
c l a s s Show a where
show :: a → S t r i n g
Endrekursiv:
g e t L i n e s :: IO S t r i n g
g e t L i n e s = g e t i t " " where
g e t i t r e s = do s t r ← g e t L i n e
i f n u l l s t r then r e t u r n r e s
e l s e g e t i t ( r e s ++ s t r )
I
Deshalb zusätzlich
s h o w s P r e c :: I n t → a → S t r i n g → S t r i n g
show x = s h o w s P r e c 0 x " "
I
String wird erst aufgebaut, wenn er ausgewertet wird (O(n)).
12 [33]
11 [33]
Beispiel: Mengen als Listen
Effizienz durch “unendliche” Datenstrukturen
I
data S e t a = S e t [ a ]
Listen müssen nicht endlich repräsentierbar sein:
I
Beispiel: “unendliche” Liste [2,2,2, . . . ]
I
Liste der natürlichen Zahlen:
I
Syntaktischer Zucker:
Zu langsam wäre
twos = 2 : twos
i n s t a n c e Show a ⇒ Show ( S e t a ) where
show ( S e t e l e m s ) =
" { " ++ i n t e r c a l a t e " , " ( map show e l e m s ) ++ " } "
n a t = n a t s 0 where n a t s n = n : n a t s ( n+ 1 )
Deshalb besser
nat = [0 . . ]
i n s t a n c e Show a ⇒ Show ( S e t a ) where
s h o w s P r e c i ( S e t e l e m s ) = showElems e l e m s where
showElems [ ]
= ( " {} " ++ )
showElems ( x : x s ) = ( ’ { ’ : ) ◦ shows x ◦ s h o w l x s
where s h o w l [ ]
= ( ’} ’ : )
s h o w l ( x : x s ) = ( ’ , ’ : ) ◦ shows x ◦ s h o w l x s
I
Bildung von unendlichen Listen:
c y c l e :: [ a ] → [ a ]
c y c l e x s = x s ++ c y c l e x s
r e p e a t :: a → [ a ]
repeat x = x : repeat x
I
Nützlich für Listen mit unbekannter Länge
I
Obacht: Induktion nur für endliche Listen gültig.
13 [33]
Berechnung der ersten n Primzahlen
I
Eratosthenes — aber bis wo sieben?
I
Lösung: Berechnung aller Primzahlen, davon die ersten n.
Fibonacci-Zahlen
s i e v e :: [ I n t e g e r ] → [ I n t e g e r ]
s i e v e ( p : ps ) =
p : ( s i e v e ( f i l t e r ( \n → n ‘ mod ‘ p /= 0 ) p s ) )
I
14 [33]
I
Aus der Kaninchenzucht.
I
Sollte jeder Informatiker kennen.
f i b :: I n t e g e r → I n t e g e r
fib 0 = 1
fib 1 = 1
f i b n = f i b ( n−1)+ f i b ( n−2)
Keine Rekursionsverankerung ( sieve [ ])
p r i m e s :: [ I n t e g e r ]
primes = s i e v e [2 . . ]
I
Von allen Primzahlen die ersten n:
I
Problem: baumartige Rekursion, exponentieller Aufwand.
f i r s t p r i m e s :: I n t → [ I n t e g e r ]
f i r s t p r i m e s n = take n primes
15 [33]
16 [33]
Fibonacci-Zahlen als Strom
Unendliche Datenstrukturen
I
Lösung: zuvor berechnete Teilergebnisse wiederverwenden.
I
Endliche Repräsentierbarkeit für beliebige Datenstrukturen
I
Sei fibs :: [ Integer ] Strom aller Fibonaccizahlen:
I
E.g. Bäume:
fibs
tail fibs
tail ( tail fibs )
I
1
1
2
1
2
3
2
3
5
3 5 8 13 21 34 55
5 8 13 21 34 55
8 13 21 34 55
data Tree a
= N u l l | Node ( Tree a ) a ( Tree a )
d e r i v i n g Show
twoTree
= Node twoTree 2 twoTree
Damit ergibt sich:
r i g h t S p l i n e n = Node N u l l n ( r i g h t S p l i n e ( n+1 ) )
f i b s :: [ I n t e g e r ]
f i b s = 1 : 1 : z i p W i t h (+) f i b s ( t a i l
fibs )
I
I
n-te Fibonaccizahl mit fibs !! n
I
I
Aufwand: linear, da fibs nur einmal ausgewertet wird.
I
twoTree, twos mit Zeigern darstellbar (e.g. Java, C)
rightSpline 0, nat nicht mit darstellbar
Damit beispielsweise auch Graphen modellierbar
17 [33]
Implementation und Repräsentation von Datenstrukturen
18 [33]
Speicherlecks
I
I
I
Datenstrukturen werden intern durch Objekte in einem Heap
repräsentiert
Garbage collection gibt unbenutzten Speicher wieder frei.
I
I
Bezeichner werden an Referenzen in diesen Heap gebunden
Verzögerte Auswertung effizient, weil nur bei Bedarf ausgewertet wird
I
I
Unendliche Datenstrukturen haben zyklische Verweise
I
I
Kopf wird nur einmal ausgewertet.
c y c l e ( t r a c e " Foo ! " [ 5 ] )
I
Anmerkung: unendlich Datenstrukturen nur sinnvoll für nicht-strikte
Funktionen
Aber Obacht: Speicherlecks!
Eine Funktion hat ein Speicherleck, wenn Speicher unnötig lange im
Zugriff bleibt.
I
I
Unbenutzt: Bezeichner nicht mehr im erreichbar
“Echte” Speicherlecks wie in C/C++ nicht möglich.
Beispiel: getLines, fac2
I
Zwischenergebnisse werden nicht auswertet.
I
Insbesondere ärgerlich bei nicht-terminierenden Funktionen.
19 [33]
Striktheit
I
I
Speicherprofil: fac1 50000, nicht optimiert
Strikte Argumente erlauben Auswertung vor Aufruf
Dadurch konstanter Platz bei Endrekursion.
Erzwungene Striktheit: seq :: α→ β→ β
fac
⊥ ‘seq‘ b = ⊥
a ‘seq‘ b = b
I
I
6,418,898 bytes x seconds
Wed Jan 19 11:24 2011
bytes
I
20 [33]
1,200k
seq vordefiniert (nicht in Haskell definierbar)
($!) :: (a→ b)→ a→ b strikte Funktionsanwendung
TSO
1,000k
800k
f $ ! x = x ‘ seq ‘ f x
BLACKHOLE
I
I
ghc macht Striktheitsanalyse
600k
Fakultät in konstantem Platzaufwand
400k
ARR_WORDS
f a c 3 :: I n t e g e r → I n t e g e r
f a c 3 n = f a c ’ n 1 where
f a c ’ n a c c = s e q a c c $ i f n == 0 then a c c
e l s e f a c ’ ( n−1) ( n∗ a c c )
200k
0k
0.0
0.5
1.0
1.5
2.0
2.5
3.0
3.5
4.0
4.5
seconds
21 [33]
Speicherprofil: fac2 50000, nicht optimiert
Wed Jan 19 11:25 2011
fac
1,481,240 bytes x seconds
bytes
7,213,527 bytes x seconds
Speicherprofil: fac3 50000, nicht optimiert
bytes
fac
22 [33]
1,200k
500k
Wed Jan 19 11:25 2011
450k
TSO
1,000k
TSO
400k
350k
800k
300k
BLACKHOLE
BLACKHOLE
250k
600k
200k
400k
150k
ARR_WORDS
ARR_WORDS
100k
200k
50k
0k
0.0
0.5
1.0
1.5
2.0
2.5
3.0
3.5
4.0
4.5
5.0
0k
0.0
seconds
23 [33]
0.5
1.0
1.5
2.0
2.5
3.0
3.5
4.0 seconds
24 [33]
Speicherprofil: fac1 50000, optimiert
Wed Jan 19 11:25 2011
fac
226,043 bytes x seconds
Wed Jan 19 11:25 2011
bytes
3,568,845 bytes x seconds
bytes
fac
Speicherprofil: fac2 50000, optimiert
80k
ARR_WORDS
TSO
600k
60k
TSO
integer-gmp:GHC.Integer.Type.S#
400k
40k
MUT_ARR_PTRS_CLEAN
ARR_WORDS
200k
20k
ghc-prim:GHC.Types.:
0k
0.0
0k
0.5
1.0
1.5
2.0
2.5
3.0
3.5
4.0
seconds
0.0
0.5
1.0
1.5
2.0
2.5
3.0
seconds
25 [33]
Speicherprofil: fac3 50000, optimiert
225,010 bytes x seconds
Fazit Speicherprofile
Wed Jan 19 11:25 2011
bytes
fac
26 [33]
80k
I
Geschwindigkeitsgewinn durch Endrekursion nur mit Striktheit
I
Optimierung des ghc meist ausreichend für Striktheitsanalyse, aber
nicht für Endrekursion
ARR_WORDS
60k
TSO
40k
MUT_ARR_PTRS_CLEAN
20k
ghc-prim:GHC.Types.:
0k
0.0
0.5
1.0
1.5
2.0
2.5
3.0
seconds
27 [33]
foldr vs. foldl
I
Wann welches fold?
foldr ist nicht endrekursiv:
I
f o l d r :: ( a → b → b ) → b → [ a ] → b
foldr f z []
= z
f o l d r f z ( x : xs ) = f x ( f o l d r f z xs )
I
28 [33]
I
foldl ist endrekursiv:
I
Strikte Funktionen mit foldl’ falten:
r e v 2 :: [ a ] → [ a ]
rev2 = f o l d l ’ ( f l i p
foldl’ ist strikt und endrekursiv:
foldl
foldl
foldl
let
foldl’ ferner strikt und konstanter Platzaufwand
Wann welches fold?
I
f o l d l :: ( a → b → a ) → a → [ b ] → a
foldl f z []
= z
f o l d l f z ( x : xs ) = f o l d l f ( f z x ) xs
I
foldl endrekursiv, aber traversiert immer die ganze Liste.
I
I
’ :: ( a → b → a ) → a → [ b ] → a
’ f a []
= a
’ f a ( x : xs ) =
a ’ = f a x i n a ’ ‘ seq ‘ f o l d l ’ f a ’ x s
(:))
[]
Wenn nicht die ganze Liste benötigt wird, mit foldr falten:
a l l :: ( a → Bool ) → [ a ] → Bool
a l l p = f o l d r ((&&) ◦ p ) True
I
Potenziell unendliche Listen immer mit foldr falten.
Für Monoid (φ, e) gilt: foldr φ e l = foldl (flip φ) e l
29 [33]
Überladene Funktionen sind langsam.
30 [33]
Listen als Performance-Falle
I
I
Typklassen sind elegant aber langsam.
I
I
I
Listen:
Implementierung von Typklassen: Verzeichnis (dictionary) von
Klassenfunktionen.
I
Überladung wird zur Laufzeit aufgelöst
I
I
I
I
Listen sind keine Felder oder endliche Abbildungen
Bei kritischen Funktionen: Spezialisierung erzwingen durch Angabe der
Signatur
Felder Array ix a (Modul Array aus der Standardbücherei )
I
I
I
NB: Zahlen (numerische Literale) sind in Haskell überladen!
I
I
I
Bsp: facs hat den Typ Num a=> a-> a
I
I
31 [33]
Feste Größe (Untermenge von ix )
Zugriff auf n-tes Element in konstanter Zeit.
Abstrakt: Abbildung Index auf Daten
Endliche Abbildung Map k v (Modul Data.Map)
I
f a c s n = i f n == 0 then 1 e l s e n∗ f a c s ( n−1)
Beliebig lang
Zugriff auf n-tes Element in linearer Zeit.
Abstrakt: frei erzeugter Datentyp aus Kopf und Rest
Beliebige Größe
Zugriff auf n-tes Element in sublinearer Zeit.
Abstrakt: Abbildung Schlüsselbereich k auf Wertebereich v
32 [33]
Zusammenfassung
I
Endrekursion: while für Haskell.
I
Überführung in Endrekursion meist möglich.
I
Noch besser sind strikte Funktionen.
I
Speicherlecks vermeiden: Striktheit und Endrekursion
I
Compileroptimierung nutzen
I
Datenstrukturen müssen nicht endlich repräsentierbar sein
I
Überladene Funktionen sind langsam.
I
Listen sind keine Felder oder endliche Abbildungen.
33 [33]
Fahrplan
Praktische Informatik 3: Einführung in die Funktionale
Programmierung
Vorlesung vom 26.01.2011: Kombinatoren
I
Teil I: Funktionale Programmierung im Kleinen
I
Teil II: Funktionale Programmierung im Großen
I
Teil III: Funktionale Programmierung im richtigen Leben
Christoph Lüth & Dennis Walter
I
Effizient Funktional Programmieren
Universität Bremen
I
Fallstudie: Kombinatoren
Wintersemester 2010/11
I
Eine Einführung in Scala
I
Rückblich & Ausblick
Rev. 1347
2 [39]
1 [39]
Kombinatoren im engeren Sinne
Kombinatoren als Entwurfsmuster
Definition (Kombinator)
Ein Kombinator ist ein punktfrei definierte Funktion höherer Ordnung.
I
I
Kombination von Basisoperationen zu komplexen Operationen
I
Kombinatoren als Muster zur Problemlösung:
Herkunft: Kombinatorlogik (Schönfinkel, 1924)
K x y
S x y z
I x
B x
B x z (y z)
B x
I
S, K , I sind Kombinatoren
I
Fun fact #1: kann alle berechenbaren Funktionen ausdrücken
I
Fun fact #2: S und K sind genug: I = S K K
I
Einfache Basisoperationen
I
Wenige Kombinationsoperationen
I
Alle anderen Operationen abgeleitet
Kompositionalität:
I
Gesamtproblem läßt sich zerlegen
I
Gesamtlösung durch Zusammensetzen der Einzellösungen
3 [39]
Beispiele
4 [39]
Beispiel #1: Parser
I
I
Parserkombinatoren
I
Grafikkombinatoren mit der HGL
I
Parser bilden Eingabe auf Parsierungen ab
I
Mehrere Parsierungen möglich
I
Backtracking möglich
Kombinatoransatz:
I
I
I
Basisparser erkennen Terminalsymbole
Parserkombinatoren zur Konstruktion:
Grafikkombinatoren mit tinySVG
I
I
Sequenzierung (erst A, dann B)
I
Alternierung (entweder A oder B)
Abgeleitete Kombinatoren (z.B. Listen A∗ , nicht-leere Listen A+ )
6 [39]
5 [39]
Modellierung in Haskell
Basisparser
I
Welcher Typ für Parser?
none :: P a r s e a b
none = c o n s t [ ]
type Parse a b = [a] → [(b, [a])]
I
Parametrisiert über Eingabetyp (Token) a und Ergebnis b
I
Parser übersetzt Token in abstrakte Syntax
I
Muss Rest der Eingabe modellieren
I
Muss mehrdeutige Ergebnisse modellieren
I
Beispiel: "a+b*c"
[
(Var "a", "+b*c"),
(Plus (Var "a") (Var "b") , "*c"),
(Plus (Var "a") (Times (Var "b") (Var "c")), "")]
Erkennt nichts:
I
Erkennt alles:
s u c e e d :: b → P a r s e a b
suceed b inp = [ ( b , inp ) ]
I
Erkennt einzelne Token:
spot
:: ( a → Bool ) → P a r s e a a
spot p [ ]
= []
s p o t p ( x : x s ) = i f p x then [ ( x , x s ) ] e l s e
[]
t o k e n :: Eq a ⇒ a → P a r s e a a
t o k e n t = s p o t ( λc → t == c )
I
7 [39]
Warum nicht none, suceed durch spot? Typ!
8 [39]
Basiskombinatoren: alt, >*>
I
Basiskombinatoren: use
Alternierung:
I
I
Erste Alternative wird bevorzugt
i n f i x 4 ‘ use ‘ , ‘ use2 ‘
u s e :: P a r s e a b → ( b → c ) → P a r s e a c
u s e p f i = map ( λ ( o , r ) → ( f o , r ) ) ( p i )
i n f i x l 3 ‘ alt ‘
a l t :: P a r s e a b → P a r s e a b → P a r s e a b
a l t p1 p2 i = p1 i ++ p2 i
I
u s e 2 :: P a r s e a ( b , c ) → ( b → c → d ) → P a r s e a d
use2 p f = use p ( uncurry f )
Sequenzierung:
I
Rückgabe weiterverarbeiten:
Rest des ersten Parsers als Eingabe für den zweiten
I
i n f i x l 5 >∗>
(>∗>) :: P a r s e a b → P a r s e a c → P a r s e a ( b , c )
(>∗>) p1 p2 i =
concatMap ( λ ( b , r ) →
map ( λ ( c , s ) → ( ( b , c ) , s ) ) ( p2 r ) ) ( p1 i )
Damit z.B. Sequenzierung rechts/links:
i n f i x l 5 ∗> , >∗
( ∗>) :: P a r s e a b → P a r s e a c → P a r s e a c
(>∗ ) :: P a r s e a b → P a r s e a c → P a r s e a b
p1 ∗> p2 = p1 >∗> p2 ‘ use ‘ snd
p1 >∗ p2 = p1 >∗> p2 ‘ use ‘ f s t
10 [39]
9 [39]
Abgeleitete Kombinatoren
Verkapselung
I
I
l i s t :: P a r s e a b → P a r s e a [ b ]
l i s t p = p >∗> l i s t p ‘ use2 ‘ ( : )
‘ alt ‘ suceed [ ]
I
A+ ::= AA∗
Nicht-leere Listen:
I
Eingabe muß vollständig parsiert werden
I
Auf Mehrdeutigkeit prüfen
p a r s e :: P a r s e a b → [ a ] → E i t h e r S t r i n g b
parse p i =
case f i l t e r ( n u l l . snd ) $ p i o f
[]
→ L e f t " Input does not p a r s e "
[ ( e , _) ] → Right e
_
→ L e f t " I n p u t i s ambiguous "
some :: P a r s e a b → P a r s e a [ b ]
some p = p >∗> l i s t p ‘ use2 ‘ ( : )
I
Hauptfunktion:
A∗ ::= AA∗ | ε
Listen:
I
NB. Präzedenzen: >*> (5) vor use (4) vor alt (3)
Schnittstelle:
I
Nach außen nur Typ Parse sichtbar, plus Operationen darauf
11 [39]
Grammatik für Arithmetische Ausdrücke
12 [39]
Abstrakte Syntax für Arithmetische Ausdrücke
I
Expr ::= Term + Term | Term
Zur Grammatik abstrakte Syntax
data Expr
Term ::= Factor * Factor | Factor
Factor ::= Variable | (Expr)
= P l u s Expr Expr
| Times Expr Expr
| Var
String
Variable ::= Char+
Char ::= a | · · · | z | A | · · · | Z
I
Hier Unterscheidung Term, Factor, Number unnötig.
13 [39]
Parsierung Arithmetischer Ausdrücke
I
I
Die Hauptfunktion
Token: Char
Parsierung von Factor
p F a c t o r :: P a r s e Char Expr
p F a c t o r = some ( s p o t i s A l p h a ) ‘ use ‘ Var
‘ a l t ‘ t o k e n ’ ( ’ ∗> pExpr >∗ t o k e n
I
I
’) ’
Lexing: Leerzeichen aus der Eingabe entfernen
p a r s e E x p r :: S t r i n g → Expr
parseExpr i =
case p a r s e pExpr ( f i l t e r ( n o t . i s S p a c e ) i ) o f
Right e → e
Left err → error err
Parsierung von Term
pTerm :: P a r s e Char Expr
pTerm =
p F a c t o r >∗ t o k e n ’ ∗ ’ >∗> p F a c t o r ‘ use2 ‘ Times
‘ alt ‘ pFactor
I
14 [39]
Parsierung von Expr
pExpr :: P a r s e Char Expr
pExpr = pTerm >∗ t o k e n ’+ ’ >∗> pTerm ‘ use2 ‘ P l u s
‘ a l t ‘ pTerm
15 [39]
16 [39]
Ein kleiner Fehler
Änderung des Parsers
I
I
Mangel: a+b+c führt zu Syntaxfehler — Fehler in der Grammatik
I
Behebung: Änderung der
Expr
Term
Factor
Variable
Char
I
Entsprechende Änderung des Parsers in pTerm
pTerm :: P a r s e Char Expr
pTerm =
p F a c t o r >∗ t o k e n ’ ∗ ’ >∗> pTerm ‘ use2 ‘ Times
‘ alt ‘ pFactor
Grammatik
::= Term + Expr | Term
::= Factor * Term | Factor
::= Variable | (Expr)
::= Char+
::= a | · · · | z | A | · · · | Z
I
. . . und in pExpr:
pExpr :: P a r s e Char Expr
pExpr = pTerm >∗ t o k e n ’+ ’ >∗> pExpr ‘ use2 ‘ P l u s
‘ a l t ‘ pTerm
Abstrakte Syntax bleibt
I
pFactor und Hauptfunktion bleiben.
18 [39]
17 [39]
Zusammenfassung Parserkombinatoren
I
Systematische Konstruktion des Parsers aus der Grammatik.
I
Kompositional:
I
I
Lokale Änderung der Grammatik führt zu lokaler Änderung im Parser
I
Vgl. Parsergeneratoren (yacc/bison, antlr, happy)
Beispiel #2:Die Haskell Graphics Library HGL
I
Kompakte Grafikbücherei für einfache Grafiken und Animationen.
I
Gleiche Schnittstelle zu X Windows (X11) und Microsoft Windows.
I
Bietet:
Struktur von Parse zur Benutzung irrelevant
I
Vorsicht bei Mehrdeutigkeiten in der Grammatik (Performance-Falle)
I
Einfache Implementierung (wie oben) skaliert nicht
I
Effiziente Implementation mit gleicher Schnittstelle auch für große
Eingaben geeignet.
I
Fenster
I
verschiedene Zeichenfunktionen
I
Unterstützung für Animation
19 [39]
Übersicht HGL
I
20 [39]
Basisdatentypen
Grafiken
I
type G r a p h i c
Winkel (Grad, nicht Bogenmaß)
type A n g l e
I
I
I
I
= Double
Atomare Grafiken:
Ellipsen, Linien, Polygone, . . .
I
I
Pinsel, Stifte und Textfarben
I
Farben
I
Punkte (Ursprung: links oben)
type P o i n t
Kombination von Grafiken
I
Dimensionen (Pixel)
type D i m e n s i o n = I n t
Modifikation mit Attributen:
= ( Dimension , D i m e n s i o n )
Überlagerung
21 [39]
Atomare Grafiken (1)
22 [39]
Atomare Grafiken (2)
I
I
Ellipse (gefüllt) innerhalb des gegeben Rechtecks
ellipse
::
line
polyline
Point → Point → Graphic
I
I
Ellipse (gefüllt) innerhalb des Parallelograms:
shearEllipse
::
Polygon (gefüllt)
polygon
::
[ Point ] → Graphic
Text:
text
Bogenabschnitt einer Ellipse (math. positiven Drehsinn):
a r c ::
Point → Point → Graphic
[ Point ] → Graphic
::
::
Point → Point → Point → Graphic
I
I
Strecke, Streckenzug:
Point → Point → Angle → Angle → Graphic
I
::
Point → String → Graphic
Leere Grafik:
emptyGraphic
23 [39]
::
Graphic
24 [39]
Modifikation von Grafiken
Farben
I
I
Andere Fonts, Farben, Hintergrundfarben, . . . :
withFont
withTextColor
withBkColor
withBkMode
w ithPen
withBrush
withRGB
withTextAlignment
::
::
::
::
::
::
::
::
Font
RGB
RGB
BkMode
Pen
Brush
RGB
Alignment
→
→
→
→
→
→
→
→
Graphic
Graphic
Graphic
Graphic
Graphic
Graphic
Graphic
Graphic
→
→
→
→
→
→
→
→
Graphic
Graphic
Graphic
Graphic
Graphic
Graphic
Graphic
Graphic
Nützliche Abkürzung: benannte Farben
data C o l o r = B l a c k | B l u e | Green | Cyan | Red
| Magenta | Y e l l o w | White
d e r i v i n g ( Eq , Ord , Bounded , Enum , I x , Show , Read )
I
Benannte Farben sind einfach colorTable :: Array Color RGB
I
Dazu Modifikator:
w i t h C o l o r :: C o l o r → G r a p h i c → G r a p h i c
w i t h C o l o r c = withRGB ( c o l o r T a b l e ! c )
26 [39]
25 [39]
Kombination von Grafiken
Fenster
I
I
getGraphic
setGraphic
Überlagerung (erste über zweiter):
overGraphic
::
Elementare Funktionen:
Graphic → Graphic → Graphic
I
Abgeleitetete Funktionen:
I
I
:: Window → IO G r a p h i c
:: Window → G r a p h i c → IO ( )
In Fenster zeichnen:
drawInWindow :: Window → G r a p h i c → IO ( )
drawInWindow w g = do
old ← getGraphic w
setGraphic w (g ‘ overGraphic ‘ old )
Verallgemeinerung:
o v e r G r a p h i c s :: [ G r a p h i c ] → G r a p h i c
o v e r G r a p h i c s = f o l d r overGraphic emptyGraphic
I
Grafik löschen
c l e a r W i n d o w :: Window → IO ( )
clearWindow w = s e t G r a p h i c w emptyGraphic
27 [39]
Ein einfaches Beispiel: der Ball
I
I
Ein einfaches Beispiel: Der Ball
Ziel: einen gestreiften Ball zeichen
Algorithmus: als Folge von konzentrischen Ellipsen
I
I
I
Start mit Eckpunkten (x1 , y1 ) und (x2 , y2 ).
Verringerung von x um ∆x , y bleibt gleich.
Dabei Farbe verändern.
...
28 [39]
I
Liste aller Farben cols
I
Listen der x-Position (y-Position ist konstant), ∆x = 25
I
Hilfsfunktion drawEllipse
d r a w B a l l :: P o i n t → P o i n t → G r a p h i c
d r a w B a l l ( x1 , y1 ) ( x2 , y2 ) =
l e t c o l s = c y c l e [ Red , Green , B l u e ]
midx = ( x2− x1 ) ‘ d i v ‘ 2
x l s = [ x1 , x1+ 25 . . midx ]
x r s = [ x2 , x2− 25 . . midx ]
drawEllipse c xl xr = withColor c $
e l l i p s e ( x l , y1 ) ( x r , y2 )
gs
= zipWith3 d r a w E l l i p s e c o l s x l s x r s
in o v e r G r a p h i c s ( r e v e r s e gs )
...
29 [39]
Ein einfaches Beispiel: Der Ball
30 [39]
Animation
Alles dreht sich, alles bewegt sich. . .
I
I
Hauptprogramm (Zeigen)
I
main :: IO ( )
main = r u n G r a p h i c s $ do
w ← openWindow " B a l l s ! " ( 5 0 0 , 5 0 0 )
drawInWindow w $ d r a w B a l l ( 2 5 , 2 5 ) ( 4 8 5 , 4 8 5 )
getKey w
closeWindow w
I
Animation: über der Zeit veränderliche Grafik
Unterstützung von Animationen in HGL:
I
Timer ermöglichen getaktete Darstellung
I
Gepufferte Darstellung ermöglicht flickerfreie Darstellung
Öffnen eines Fensters mit Animationsunterstützung:
I
Initiale Position, Grafikzwischenpuffer, Timer-Takt in Millisekunden
openWindowEx ::
T i t l e → Maybe P o i n t → S i z e →
RedrawMode → Maybe Time → IO Window
data RedrawMode
= Unbuffered | DoubleBuffered
31 [39]
32 [39]
Der springende Ball
I
I
Bewegung des Balles
Ball hat Position und Geschwindigkeit:
I
Geschwindigkeit ~v zu Position ~p addieren
data B a l l = B a l l { p ::
v ::
I
In X-Richtung: modulo Fenstergröße 500
I
In Y-Richtung: wenn Fensterrand 500 erreicht, Geschwindigkeit
invertieren
I
Geschwindigkeit in Y-Richtung nimmt immer um 1 ab
Point ,
Point }
Ball zeichnen: Roter Kreis an Position ~p
d r a w B a l l :: B a l l → G r a p h i c
d r a w B a l l ( B a l l {p= p } ) =
w i t h C o l o r Red ( c i r c l e p 1 5 )
I
move ( B a l l {p= ( px , py ) , v= ( vx , vy ) } )=
B a l l {p= ( px ’ , py ’ ) , v= ( vx , vy ’ ) } where
px ’ = ( px+ vx ) ‘ mod ‘ 500
py0 = py+ vy
py ’ = i f py0> 500 then 500−( py0 −500) e l s e py0
vy ’ = ( i f py0> 500 then −vy e l s e vy )+ 1
Kreis zeichnen:
c i r c l e :: P o i n t → I n t → G r a p h i c
c i r c l e ( px , py ) r = e l l i p s e ( px− r , py− r ) ( px+ r , py+ r )
33 [39]
Der springende Ball
I
Zusammenfassung HGL
Hauptschleife: Ball zeichnen, auf Tick warten, Folgeposition
loop w b =
do s e t G r a p h i c w ( d r a w B a l l b )
getWindowTick w
l o o p w ( move b )
I
34 [39]
Hauptprogram: Fenster öffnen, Starten der Hauptschleife
main = r u n G r a p h i c s $
do w← openWindowEx " Bounce ! " N o t h i n g ( 5 0 0 , 5 0 0 )
DoubleBuffered ( J u s t 30)
l o o p w ( B a l l {p=( 0 , 1 0 ) , v= ( 5 , 0 ) } )
I
Abstrakte und portable Grafikprogrammierung
I
Verkapselung von systemnaher Schnittstelle durch Kombinatoren
I
Kombinatoransatz: Kombination elementarer Grafiken zu komplexen
Grafikprogrammen
I
Rudimentäre Unterstützung von Animation durch Timer und Puffer
I
Kombinatoransatz hier:
type Time = I n t
type A n i m a t i o n = I n t → G r a p h i c
35 [39]
Beispiel #3: tinySVG
I
I
I
I
tinySVG in Aktion
s n a i l S h e l l :: I n t → G r a p h i c s
snailShell n =
let rs = [ fromInt i / fromInt n | i ← [ 1 . . n ] ]
t h e L i n e = l i n e ( pt ’ 0 0 ) ( pt ’ 100 0 )
l i n e s = [ r o t a t e ( r ∗ 360) $ s c a l e r t h e L i n e
| r ← rs ]
in group l i n e s
Scalable Vector Graphics (SVG)
I
XML-Standard für Vektorgrafiken
Unterstützt Vektorgrafiken (Pfade), Rastergrafiken und Text
Ein Kreis: <circle x="20" y="30" r="50" />
Übersetzung in Kombinatorbücherei in Haskell (tinySVG):
I
Elementare Operationen:
circle
line
I
::
::
36 [39]
P o i n t → Double → G r a p h i c s
Point → Point → Graphics
Kombinatoren zum Transformieren:
g r o u p :: [ G r a p h i c s ] → G r a p h i c s
r o t a t e :: Double → G r a p h i c s → G r a p h i c s
s c a l e :: Double → G r a p h i c s → G r a p h i c s
I
Ausgabe:
toXML ::
Double → Double → G r a p h i c s → S t r i n g
37 [39]
Zusammenfassung
I
I
I
I
Kombinatoransatz:
I
Einfache Basisoperationen
I
Wenige Kombinationsoperatoren
I
Ideal in funktionalen Sprachen, generell nützlich
Parserkombinatoren:
I
Von Grund auf in Haskell
I
Kombinatoren abstrahieren über Implementation
Grafik mit HGL:
I
Verkapselung von Systemschnittstellen
I
Kombinatoren abstrahieren Systemschnittstellen
Grafik mit tinySVG:
I
Kombinatoren abstrahieren über XML-Syntax
39 [39]
38 [39]
Fahrplan
Praktische Informatik 3: Einführung in die Funktionale
Programmierung
Vorlesung vom 02.02.2011: The Next Big Thing — Scala
I
Teil I: Funktionale Programmierung im Kleinen
I
Teil II: Funktionale Programmierung im Großen
I
Teil III: Funktionale Programmierung im richtigen Leben
Christoph Lüth & Dennis Walter
I
Effizient Funktional Programmieren
Universität Bremen
I
Fallstudie: Kombinatoren
Wintersemester 2010/11
I
Eine Einführung in Scala
I
Rückblich & Ausblick
Rev. 1368
2 [21]
1 [21]
Scala
Scala — Die Sprache
I
I
A scalable language
I
Multi-paradigm language: funktional + oobjektorientiert
I
„Lebt im Java-Ökosystem“
I
I
I
Martin Odersky, ETH Lausanne
http://www.scala-lang.org/
Objekt-orientiert:
I
Veränderlicher, gekapselter Zustand
I
Klassen und Objekte
I
Subtypen und Vererbung
Funktional:
I
Algebraische Datentypen
I
Unveränderliche Werte
I
Parametrische Polymorphie
I
Funktionen höherer Ordnung
3 [21]
Scala ist skalierbar
I
„A language that grows on you.“
I
Statt fertiger Sprache mit vielen Konstrukten Rahmenwerk zur
Implementation eigener Konstrukte:
I
I
I
Einfache Basis
I
Flexible Syntax
I
Flexibles Typsystem
Durchgängige Objektorientierung
I
Alles in Scala ist ein Objekt
I
I
Einfach zu benutzen:
Keine primitiven Typen
Operatoren sind Methoden
I
Nachteil: Easy to learn but hard to master.
I
4 [21]
Beliebig überladbar
I
Kein static, sondern Singleton-Objekte (object)
I
Beispiel: Rational.scala
Leichtgewichtig durch Typinferenz und Interpreter
5 [21]
Werte
I
Zuweisung an Werte nicht erlaubt
I
Dadurch unveränderliche Objekte −→ referentielle Transparenz
I
“Unreine” Sprache
I
lazy val: wird nach Bedarf ausgewertet.
I
Listen mit pattern matching
I
Funktionen höherer Ordnung
I
Listenkomprehension
I
Beispiel: Queens.scala
Sonderbehandlung von Endrekursion für bessere Effizienz
I
I
Funktionale Aspekte
Veränderliche Variablen var, unveränderliche Werte val
I
I
6 [21]
Damit effiziente funktionale Programmierung möglich
Beispiel: Gcd.scala
7 [21]
8 [21]
Algebraische Datentypen
I
Algebraische Datentypen und Vererbung
Case Classes
I
Konzise Syntax für Konstruktoren: factory methods, kein new
I
Parameter werden zu val-Feldern
I
Pattern Match mit Selektoren
I
Disjunkte Vereinigung durch Vererbung
I
Beispiel: Expr.scala
I
Algebraische Datentypen können erweitert werden.
I
Beispiel Expr:
case c l a s s UnOp( op : S t r i n g , e : Expr ) extends Expr
I
Vorteil: flexibel
I
Nachteil: Fehleranfällig
I
Verbieten der Erweiterung: sealed classes
s e a l e d a b s t r a c t c l a s s Expr
...
10 [21]
9 [21]
Scala: Klassenhierarchie
Polymorphie und Subtypen
I
Generische Typen (Scala) ∼
= Parametrische Polymorphie (Haskell)
I
I
In Scala besser als in Java wegen Typinferenz
Problem mit generischen Typen und Polymorphie: Varianz
I
Gegeben List [T]
I
Ist List [ String ] < List [AnyRef]?
I
Gilt das immer? Nein!
11 [21]
Typvarianz
12 [21]
Java: das gleiche Problem
Gegeben folgende Klasse:
Gegeben:
c l a s s C e l l [ T ] ( i n i t : T) {
p r i v a t e va r c u r r = i n i t
def g e t = c u r r
def s e t ( x : T) = { c u r r = x }
}
S t r i n g [ ] a1 = { " abc " } ;
O b j e c t [ ] a2 = a1 ;
a2 [ 0 ]= new I n t e g e r ( 1 ) ;
S t r i n g s = a1 [ 0 ] ;
Problem: Ist Cell [ String ] < Cell [Any]?
Bei Ausführung Laufzeitfehler:
v a l c1 = new C e l l [ S t r i n g ] ( " abc " )
v a l c2 : C e l l [ Any ] = c1
c2 . s e t ( 1 )
v a l s : S t r i n g = c1 . g e t
# javac Covariance.java
# java Covariance
Exception in thread "main" java.lang.ArrayStoreException: java.lang.I
at Covariance.main(Covariance.java:8)
Also: Cell [ String ] kein Untertyp von Cell [Any]
13 [21]
Das Problem: Kontravarianz vs. Kovarianz
I
I
I
Links des Funktionspfeils (Argument)
Kehrt Subtypenbeziehung
Position der Typvariablen bestimmt Varianz:
I
I
I
I
a b s t r a c t c l a s s L i s t [+T ]
case o b j e c t N i l extends L i s t [ N o t h i n g ]
case c l a s s :: [ T ] ( hd : T , t l : L i s t [ T ] ) extends L i s t [ T ]
Rechts des Funktionspfeils (Resultat)
Erhält Subtypenbeziehung
Kontravariant:
I
I
Gegeben Listen:
Nicht var und Zuweisung
Kovariant:
I
I
Beschränkte Polymorphie
Problem ist Typ von set : T =>Cell[T]=>()
I
I
14 [21]
Gegeben Mengen A, B, X mit A ⊆ B
Dann ist X → A ⊆ X → B
Aber nicht A → X ⊆ B → X
I
Problem: An List [T] kann nur T gehängt werden
I
Wünschenswert: beliebiger Untertyp von T
I
Lösung: bounded polymorphism
case c l a s s
:: [ U >: T ) ( hd : U , t l : L i s t [ T ] )
extends L i s t [ T ]
Annotation der Varianz: Set[+T], Map[−T]
15 [21]
16 [21]
Traits
Weitere Besonderheiten: apply
t r a i t Ordered [A] {
def cmp ( a : A ) : I n t
def
def
def
def
def
< ( a : A ) : B o o l e a n = ( t h i s cmp a ) < 0
> ( a : A ) : B o o l e a n = ( t h i s cmp a ) > 0
≤ ( a : A ) : B o o l e a n = ( t h i s cmp a ) ≤ 0
≥ ( a : A ) : B o o l e a n = ( t h i s cmp a ) ≥ 0
cmpTo ( t h a t : A ) : I n t = cmp ( t h a t ) }
c l a s s R a t i o n a l extends O r d e r e d [ R a t i o n a l ] {
...
def cmp ( r : R a t i o n a l ) =
( t h i s . numer ∗ r . denom ) − ( r . numer ∗ t h i s . denom )
I
I
I
I
apply erlaubt Definition von Factory-Methoden und mehr:
I
f ( i ) wird syntaktisch zu f . apply( i )
I
Anderes Beispiel: Selektion aus array , Funktionen
Mächtiger als Interfaces (Java): kann Implementierung enthalten
Mächtiger als abtrakte Klassen: Mehrfachvererbung
Mächtiger als Typklassen (Haskell): mehr als ein Typ-Parameter
17 [21]
Weitere Besonderheiten: Extraktoren
Weitere Besonderheiten
o b j e c t EMail {
def a p p l y ( u s e r : S t r i n g , domain : S t r i n g ) =
u s e r+ "@"+ domain
def u n a p p l y ( s t r : S t r i n g ) : O p t i o n [ ( S t r i n g , S t r i n g ) ] =
{ v a l ps = s t r s p l i t "@"
i f ( ps . l e n g t h == 2 ) Some ( p s ( 0 ) , p s ( 1 ) ) e l s e None
}
}
I
18 [21]
Extraktoren erlauben erweitertes pattern matching:
I
Native XML support, Beispiel: CCTherm.scala
I
Implizite Konversionen und Parameter
v a l s = EMail ( " c x [email protected] d f k i . de " )
s match { case EMail ( u s e r , domain ) => . . .}
I
Typgesteuertes pattern matching:
v a l x : Any
x match { case EMail ( u s e r , domain ) => . . .}
19 [21]
Zusammenfassung
I
Haskell + Java = Scala (?)
I
Skalierbare Sprache:
I
I
mächtige Basiskonstrukte
I
plus flexibles Typsystem
I
plus flexible Syntax (“syntaktischer Zucker”)
Die Zukunft von Java?
21 [21]
20 [21]
Fahrplan
Praktische Informatik 3: Einführung in die Funktionale
Programmierung
Vorlesung vom 09.02.11: Schlußbemerkungen
I
Teil I: Funktionale Programmierung im Kleinen
I
Teil II: Funktionale Programmierung im Großen
I
Teil III: Funktionale Programmierung im richtigen Leben
Christoph Lüth & Dennis Walter
I
Effizient Funktional Programmieren
Universität Bremen
I
Fallstudie: Kombinatoren
Wintersemester 2010/11
I
Eine Einführung in Scala
I
Rückblich & Ausblick
Rev. 1371
Organisatorisches
I
Ausgefüllten Scheinvordruck zum Fachgespräch mitbringen
I
Nur wer ausgefüllten Scheinvordruck abgibt, erhält auch einen.
I
Evaluationsbogen ausfüllen
2 [24]
1 [24]
Beispiel
Definieren Sie eine Funktion leastSpaces, welche aus
einer Liste von Zeichenketten diejenige zurückgibt,
welche die wenigsten Leerzeichen enthält.
3 [24]
Inhalt
I
Wiederholung
I
Rückblick, Ausblick
4 [24]
Vorlesung vom 27.10.10: Grundbegriffe
I
Was sind die Bestandteile einer Funktionsdefinition?
I
Was bedeutet referentielle Transparenz?
I
Was ist eigentlich syntaktischer Zucker? (Beispiel?)
6 [24]
5 [24]
Vorlesung vom 03.11.10: Funktionen und Datentypen
I
Welche Auswertungsstrategien gibt es, welche benutzt Haskell?
I
Was bedeutet Striktheit?
I
Was ist die Abseitsregel?
I
Welche vordefinierten Basisdatentypen gibt es in Haskell?
Vorlesung vom 10.11.10: Rekursive Datentypen
I
Welches sind die wesentlichen Eigenschaften der Konstruktoren eines
algebraischen Datentyps?
I
Was ist das:
data X a = X a [ X a ]
I
Was bedeutet strukturelle Induktion?
I
Wie beweist man folgende Behauptung:
map f ( map g x s ) = map ( f . g ) x s
7 [24]
8 [24]
Vorlesung vom 17.11.10: Typvariablen und Polymorphie
I
Was ist parametrische Polymorphie in funktionalen Sprachen?
I
Wo ist der Unterschied zu Polymorphie Java, und was entspricht der
parametrischen Polymorphie in Java?
I
Welche Standarddatentypen gibt es in Haskell?
I
Wozu dienen Typklassen in Haskell?
Vorlesung vom 24.11.10: Funktionen höherer Ordnung
I
Was ist eine Funktion höher Ordnung?
I
Was ist einfache Rekursion?
I
Was ist Listenkomprehension?
I
Wie läßt sich Listenkomprehension durch map und filter darstellen?
I
Was ist der Unterschied zwischen foldr und foldl ?
I
. . . und wann benutzt man welches?
10 [24]
9 [24]
Vorlesung vom 01.12.10: Typinferenz
I
Woran kann Typableitung scheitern?
I
Was ist der Typ von λx y→ (x, 3) : [( " ", y )]
I
. . . und warum?
I
Was ist ein Beispiel für einen Ausdruck vom Typ [([ a ], Int )] ?
Vorlesung vom 08.12.2010: ADTs
I
Was ist ein abstrakter Datentyp?
I
Wieso sind geordnete Bäume ein abstrakter und kein algebraischer
Datentyp?
I
Wieso sind Listen ein algebraischer und kein abstrakter Datentyp?
I
Haben abstrakte Datentypen einen verkapselten Zustand?
I
Was ist ein Modul in Haskell, und was sind seine Bestandteile?
12 [24]
11 [24]
Vorlesung vom 05.01.11: Signaturen und Eigenschaften
I
Was ist eine Signatur?
I
Was sind Axiome?
I
Was wäre ein ADT für Array a — welche Operationen, welche
Eigenschaften?
Vorlesung vom 12.01.11: Aktionen und Zustände
I
Was unterscheidet Aktionen (IO a) von anderen ADTs?
I
Was sind die Operationen des ADT IO a?
I
Wozu dient return?
I
Welche Eigenschaften haben die Operationen?
I
Wie kann man. . .
I
. . . aus einer Datei lesen?
I
. . . die Kommandozeilenargumente lesen?
I
. . . eine Zeichenkette ausgeben?
13 [24]
Vorlesung vom 19.01.11: Effizienzaspekte
I
14 [24]
Zusammenfassung Haskell
Stärken:
Abstraktion durch
Was ist Endrekursion?
I
I
I
I
I
Was ist ein Speicherleck in Haskell?
I
Wie vermeide ich Speicherlecks?
15 [24]
Polymorphie und Typsystem
algebraische Datentypen
Funktionen höherer Ordnung
I
Flexible Syntax
I
Haskell als Meta-Sprache
I
Ausgereifter Compiler
I
Viele Büchereien
Schwächen:
I
I
Komplexität
Dokumentation
I
I
I
I
I
I
z.B. im Vergleich zu Java’s APIs
Büchereien
Noch viel im Fluß
Tools ändern sich
Zum Beispiel HGL
Entwicklungsumgebungen
16 [24]
Andere Funktionale Sprachen
I
I
Andere Funktionale Sprachen
Standard ML (SML):
I
Streng typisiert, strikte Auswertung
I
Formal definierte Semantik
I
Drei aktiv unterstütze Compiler
I
Verwendet in Theorembeweisern (Isabelle, HOL)
I
http://www.standardml.org/
I
Caml, O’Caml:
I
Streng typisiert, strikte Auswertung
I
Hocheffizienter Compiler, byte code & nativ
I
Nur ein Compiler (O’Caml)
I
http://caml.inria.fr/
LISP & Scheme
I
Ungetypt/schwach getypt
I
Seiteneffekte
I
Viele effiziente Compiler, aber viele Dialekte
I
Auch industriell verwendet
17 [24]
Funktionale Programmierung in der Industrie
I
I
I
I
Perspektiven
Erlang
I
schwach typisiert, nebenläufig, strikt
I
Fa. Ericsson — Telekom-Anwendungen
18 [24]
I
I
FL
I
ML-artige Sprache
I
Chip-Verifikation der Fa. Intel
I
Galois Connections
I
Hochqualitätssoftware in Haskell
I
Hochsicherheitswebserver, Cryptoalgorithmen
Verschiedene andere Gruppen
Funktionale Programmierung in 10 Jahren?
Anwendungen:
I
Integration von XML, DBS (X#/Xen, Microsoft)
I
Integration in Rahmenwerke (F# & .Net, Microsoft)
I
Eingebettete domänenspezifische Sprachen
Forschung:
I
Ausdrucksstärkere Typsysteme
I
für effiziente Implementierungen
I
und eingebaute Korrektheit (Typ als Spezifikation)
I
Parallelität?
19 [24]
Warum funktionale Programmierung nie Erfolg haben wird
I
Programmierung nur kleiner Teil der SW-Entwicklung
I
Mangelnde Unterstützung:
20 [24]
Warum funktionale Programmierung lernen?
I
Denken in Algorithmen, nicht in Programmiersprachen
I
Abstraktion: Konzentration auf das Wesentliche
I
I
I
I
Libraries, Dokumentation, Entwicklungsumgebungen
Nicht verbreitet — funktionale Programmierer zu teuer
Wesentliche Elemente moderner Programmierung:
I
Datenabstraktion und Funktionale Abstraktion
I
Modularisierung
I
Typisierung und Spezifikation
Konservatives Management
I
I
Blick über den Tellerrand — Blick in die Zukunft
I
Studium 6= Programmierkurs — was kommt in 10 Jahren?
“Nobody ever got fired for buying IBM”
21 [24]
22 [24]
Hilfe!
I
I
I
I
Haskell: primäre Entwicklungssprache am DFKI, FG SKS
I
Formale Programmentwicklung: http://www.tzi.de/cofi/hets
I
Sicherheit in der Robotik: http://www.dfki.de/sks/sams
Tschüß!
Wir suchen studentische Hilfskräfte für diese Projekte
Wir bieten:
I
Angenehmes Arbeitsumfeld
I
Interessante Tätigkeit
Wir suchen Tutoren für PI3
I
im WS 11/12 — meldet Euch bei Berthold Hoffmann!
23 [24]
24 [24]
Herunterladen