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 ( l@ ( Branch l k l a _ _ ) ) ( r@ ( 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 l@ 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]