8 · Listenverarbeitung List Comprehensions · 8.5 Operationale Semantik für List Comprehensions Die Semantik der List Comprehensions, welche wir vorhin (cf. Seite 237) eher durch “hand-waving” erklärt haben, lässt sich —ganz ähnlich wie bei der β-Reduktion des λ-Kalküls— durch Reduktionsregeln formal erklären. Definition Semantik der List Comprehension, ohne Pattern Matching Sei e ein Haskell-Ausdruck, v Variable, qs eine Sequenz32 von Qualifiern. Die folgenden Regeln reduzieren jeweils den ersten Qualifier: ○ 1 [ e | v <- [], qs ] � [] [ e | v <- (x : xs), qs ] � [ e | qs ][v � x] 2 ++ [ e | v <- xs, qs ] ○ [ e | False, qs ] � [] [ e | True, qs ] � [ e | qs ] 32 qs [ e | ] � [ e ] ○ 3 ○ 4 ○ 5 ist keine Haskell-Liste, sondern ein Konstrukt der Meta-Ebene, cf. Seite 237. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 243 8 · Listenverarbeitung List Comprehensions · 8.5 � Die ersten beiden Reduktionsregeln reduzieren einen Generator über einer 1 bzw. nichtleeren ○ 2 Liste. leeren ○ � 3 und ○ 4 testen Prädikate. Regeln ○ � 5 ist anwendbar, sobald die Sequenz der Qualifier vollständig Regel ○ reduziert wurde. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 244 8 · Listenverarbeitung Beispiel List Comprehensions · 8.5 Reduktion von [ x^2 | x <- [1,2,3], odd x ] [ x^2 | x <- [1,2,3], odd x ] � = � � � � 2 verwenden ○ [ x^2 | odd x ][x � 1] ++ [ x^2 | x <- [2,3], odd x ] �� � � A [ 1^2 | odd 1 ] ++ A [ 1^2 | True ] ++ A 4 verwenden ○ [ 1^2 | ] ++ A 5 verwenden ○ [1^2] ++ A A � �� � 1 : [ x^2 | x <- [2,3], odd x ] Stefan Klinger · DBIS Informatik 2 · Sommer 2016 245 8 · Listenverarbeitung List Comprehensions · 8.5 1 : [ x^2 | x <- [2,3], odd x ] � = � � � � � = 2 verwenden ○ 1 : [ x^2 | odd x ][x � 2] ++ [ x^2 | x <- [3], odd x ] �� � � 1 : [ 2^2 | odd 2 ] ++ B B 1 : [ 2^2 | False ] ++ B 3 verwenden ○ 1 : [] ++ B B � �� � 1 : [ x^2 | x <- [3], odd x ] 2 verwenden ○ 1 : [ x^2 | odd x ][x � 3] ++ [ x^2 | x <- [], odd x ] 1 links wie gehabt (� 9), und rechts verwenden wir ○ 1 : 9 : [] [1, 9] Stefan Klinger · DBIS Informatik 2 · Sommer 2016 246 8 · Listenverarbeitung List Comprehensions · 8.5 Abbildung von List Comprehensions auf den Haskell-Kern � Prinzipiell erlaubt das System der eben besprochenen Reduktionsregeln, List Comprehensions auf in Haskell vordefinierte Funktionen zurückzuführen. � Im Folgenden betrachten wir ein Übersetzungsschema �·�, das List Comprehensions aus Haskell-Code entfernt33 : � � Code mit List Comprehension � = Code ohne List Comprehension �·� kann vom Compiler auf Haskell-Quellcode angewandt werden, um äquivalenten Code ohne List-Comprehensions zu erhalten. 33 Hier verwenden wir semantische Klammern etwas anders als gewohnt. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 247 8 · Listenverarbeitung 1 2 List Comprehensions · 8.5 Dabei basiert das Schema �·� auf der Funktion concatMap: concatMap :: (α -> [β]) -> [α] -> [β] concatMap f = foldr (\x xs -> f x ++ xs) [] Frage Was tut diese Funktion? Stefan Klinger · DBIS Informatik 2 · Sommer 2016 248 8 · Listenverarbeitung 1 2 List Comprehensions · 8.5 concatMap :: (α -> [β]) -> [α] -> [β] concatMap f = foldr (\x xs -> f x ++ xs) [] 3 4 5 > concatMap (replicate 3) "hello" "hhheeellllllooo" Antwort concatMap f xs wendet die Funktion f auf jedes Element von xs an. Dabei gibt f jeweils eine Liste zurück, welche von concatMap konkateniert werden. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 249 8 · Listenverarbeitung List Comprehensions · 8.5 Übersetzungsschema �·� immer noch ohne Pattern Matching Sei e ein Ausdruck, v eine Variable, b ein Boolescher Ausdruck, und qs wieder eine Sequenz von Qualifiern. �[ e | v <- xs, qs ]� = concatMap (λv . �[ e | qs ]�) �xs� ○ 1 2 �[ e | b, qs ]� = if �b� then �[ e | qs ]� else [] ○ �[ e | ]� = [ �e� ] �e� = Wende �·� rekursiv auf alle nicht-primitiven ○ 3 ○ 4 Teilausdrücke von e an. � � 1 : Generatoren; ○ 2 : Prädikate) Wieder wird die Sequenz der Qualifier (○ ○ reduziert, bis 3 den Fall ohne Qualifier behandeln kann. 4 steigt rekursiv im AST ab, um evtl. weitere List Regel ○ Comprehensions zu übersetzen. 2 , statt einfach b zu schreiben? Frage Wozu brauchen wir �b� in Regel ○ Stefan Klinger · DBIS Informatik 2 · Sommer 2016 250 8 · Listenverarbeitung List Comprehensions · 8.5 Beispiel Übersetzung von [ x^2 | x <- [1..5], odd x ] = = = = �[ x^2 | x <- [1..5], odd x ]� ○ 1 concatMap (λx. �[ x^2 | odd x ]�) �[1..5]� ○ 4 concatMap (λx. �[ x^2 | odd x ]�) [1..5] ○ 2 concatMap (λx. if �odd x� then �[ x^2 | ]� else []) [1..5] ○ 3 und 2 × ○ 4 concatMap (λx. if odd x then [ x^2 ] else []) [1..5] Frage Bisher haben wir Pattern Matching in unserem Übersetzungsschema �·� nicht berücksichtigt. Wo müssen wir nachbessern? Stefan Klinger · DBIS Informatik 2 · Sommer 2016 251 8 · Listenverarbeitung List Comprehensions · 8.5 Pattern Matching in List Comprehensions Beispiel 1 Was tut die Funktion heads? Was ist der Typ? heads xs = [ y | (y:_) <- xs ] Übersetzen heads mit dem Übersetzungsschema �·�: = = = �λxs. [ y | (y:_) <- xs ]� ○ 4 λxs. �[ y | (y:_) <- xs ]� ○ 1 , behandeln Pattern wie Variable � � λxs. concatMap λ(y:_). �[ y | ]� �xs� ○ 3,○ 1 � � λxs. concatMap λ(y:_). [y] xs � � � η concatMap λ(y:_). [y] � � Frage Gilt heads ≡ concatMap λ(y:_). [y] ? Stefan Klinger · DBIS Informatik 2 · Sommer 2016 252 8 · Listenverarbeitung List Comprehensions · 8.5 Antwort Nein: Wenn der Pattern Match gegen (y:_) fehlschlägt, wird das Programm abgebrochen! 1 2 3 4 > heads [[1],[2,3],[4,5,6]] [1,2,4] > concatMap (\(y:_)-> [y]) [[1],[2,3],[4,5,6]] [1,2,4] 5 6 7 8 9 > heads [[1],[],[4,5,6]] [1,4] > concatMap (\(y:_)-> [y]) [[1],[],[4,5,6]] [1*** Exception: <interactive>:23:12-23: Non-exhaustive patterns in lambda Frage An welcher Stelle können wir das fixen? Stefan Klinger · DBIS Informatik 2 · Sommer 2016 253 8 · Listenverarbeitung List Comprehensions · 8.5 Definition Übersetzungsschema �·� mit Pattern Matching Sei e ein Ausdruck, p ein Pattern, v eine neue Variable, b ein Boolescher Ausdruck, und qs wieder eine Sequenz von Qualifiern. � ○ 1 �[ e | p <- xs, qs ]� = concatMap λv . case v of p → �[ e | qs ]� � _ → [] �xs� 2 �[ e | b, qs ]� = if �b� then �[ e | qs ]� else [] ○ �[ e | ]� = [ �e� ] �e� = Wende �·� rekursiv auf alle nicht-primitiven ○ 3 ○ 4 Teilausdrücke von e an. � � � Variable v matcht gegen jeden Wert aus �xs� (cf. Seite 151), case prüft dann ob v auf Pattern p matcht (cf. Seite 157), für fehlgeschlagene Matches werden keine Ergebnisse erzeugt. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 254 8 · Listenverarbeitung Beispiel List Comprehensions · 8.5 Nochmal: heads xs = [ y | (y:_) <- xs ] Übersetzen heads mit dem Übersetzungsschema �·�: = = �λxs. [ y | (y:_) <- xs ]� ○ 4 λxs. �[ y | (y:_) <- xs ]� ○ 1 , diesmal richtig � λxs. concatMap λv . case v of (y:_) → �[ y | ]� � _ → [] �xs� ○ 3,○ 1 = � � λxs. concatMap λv . case v of { (y:_) → [y]; _ → [] } xs � � � η concatMap λv . case v of { (y:_) → [y]; _ → [] } Jetzt gilt: � � heads ≡ concatMap λv . case v of { (y:_) → [y]; _ → [] } Stefan Klinger · DBIS Informatik 2 · Sommer 2016 255 8 · Listenverarbeitung List Comprehensions · 8.5 Weiterführende Literatur Richard Bird, and Philip Wadler. Introduction to Functional Programming using Haskell. Prentice Hall International, Series in Computer Science, 1998. Jeroen, Fokker. Functional Programming. Department of Computer Science, Utrecht University, 1995. http://www.staff.science.uu.nl/~fokke101/courses/fp-eng.pdf Torsten Grust, and Marc H. Scholl. How to Comprehend Queries Functionally. Journal of Intelligent Information Systems, vol. 12, p. 191–218, 1999. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 256 9 Algebraische Datentypen 9 · Algebraische Datentypen Dieses Kapitel erweitert Haskells Typsystem, das neben Basistypen Integer, Float, Char, Bool, ...) und den Typkonstruktoren ([ · ], (,)... und ->) auch algebraische Datentypen kennt. � Ganz analog zum Typkonstruktor [ · ], der die beiden Konstruktorfunktionen (:) und [] einführte, um Werte des Typs [α] zu konstruieren, kann der Programmierer neue Konstruktoren definieren, um Werte eines neuen algebraischen Datentyps zu erzeugen. � Wie bei Listen und Tupeln möglich, können Werte dieser neuen Typen dann mittels Pattern Matching wieder analysiert (dekonstruiert) werden. In der Tat ist der eingebaute Typkonstruktor [α] selbst ein algebraischer Datentyp (s. unten). Stefan Klinger · DBIS Informatik 2 · Sommer 2016 258 9 · Algebraische Datentypen 9.1 Deklaration eines algebraischen Datentyps · 9.1 Deklaration eines algebraischen Datentyps Mittels einer data-Deklaration wird ein neuer algebraischer Datentyp spezifiziert mit: � dem NamenT des Typkonstruktors (Identifier beginnend mit Zeichen ∈ {A...Z}) und seinen Typparametern αj , � den Namen Ki der Konstrukturfunktionen (Identifier beginnend mit Zeichen ∈ {A...Z}) und der Typen βik , die diese als Parameter erwarten. Syntax einer data-Deklaration mit n ≥ 0, m ≥ 1, ni ≥ 0, die βik sind entweder Typbezeichner oder βik = αj : data T α1 α2 ... αn = | .. . | K1 β11 ... β1n1 K2 β21 ... β2n2 Km βm1 ... βmnm Dieses data-Statement deklariert einen Typkonstruktor T und m Konstruktorfunktionen: Ki :: βi1 → ... → βini → T α1 α2 ... αn Stefan Klinger · DBIS Informatik 2 · Sommer 2016 259 9 · Algebraische Datentypen Deklaration eines algebraischen Datentyps · 9.1 Sonderfälle 1. Die Ki haben keine Argumente, also ni = 0, n = 0. data T = K1 | K2 | · · · | Km T ist damit ein reiner Summentyp (auch: Aufzählungstyp) wie aus vielen Programmiersprachen bekannt (etwa in C: enum). • Die Konstruktoren haben alle den gleichen Typ, und bilden den Wertevorrat von T : Ki :: T • Der Typ Bool ist ein Aufzählungstyp: data Bool = False | True • Dies gilt theoretisch ebenso für die anderen Basisdatentypen in Haskells Typsystem: 1 2 data Int = -2^63 | ... | -1 | 0 | 1 | ... | 2^63-1 — Pseudo-Code! data Char = ’a’ | ’b’ | ... | ’A’ | ... | ’1’ | ... Stefan Klinger · DBIS Informatik 2 · Sommer 2016 260 9 · Algebraische Datentypen Deklaration eines algebraischen Datentyps · 9.1 2. Es gibt nur eine Konstruktorfunktion, also m = 1, β1i = αi . data T α1 ... αn = K1 α1 ... αn T verhält sich damit ähnlich wie der Tupelkonstruktor und wird auch Produkttyp genannt. In der Typtheorie oft als β11 × β12 × · · · × β1n1 notiert. • Die (fest eingebauten) Definitionen von Tupeln entsprechen also: data (α,β) = (,) α β data (α,β,γ) = (,,) α β γ Allgemein führt die data-Deklaration also Alternativen (Summe) von Produkttypen ein, bezeichnet als sum-of-product types. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 261 9 · Algebraische Datentypen Deklaration eines algebraischen Datentyps · 9.1 Arbeiten mit Aufzählungstypen Beispiel Der benutzerdefinierte Aufzählungstyp data Weekday = Mon | Tue | Wed | Thu | Fri | Sat | Sun definiert � den Typkonstruktor Weekday und � die Konstruktorfunktionen Mon, ..., Sun mit z.B. Mon :: Weekday. Funktionen über algebraischen Datentypen werden mittels Pattern Matching realisiert: 1 2 3 4 weekend weekend weekend weekend :: Weekday -> Bool Sat = True Sun = True _ = False Stefan Klinger · DBIS Informatik 2 · Sommer 2016 262 9 · Algebraische Datentypen Deklaration eines algebraischen Datentyps · 9.1 Ausgabe von Aufzählungstypen Bei der Arbeit mit diesen neuen Typen reagiert Haskell merkwürdig: 1 2 3 4 5 6 7 8 9 *Main> Mon <interactive>:7:1: No instance for (Show Weekday) arising from a use of ‘print’ In a stmt of an interactive GHCi command: print it *Main> Tue == Fri <interactive>:8:5: No instance for (Eq Weekday) arising from a use of ‘==’ In the expression: Tue == Fri In an equation for ‘it’: it = Tue == Fri 1. Das Haskell-System hat keine Methode show für die Ausgabe von Werten des Typs Weekday mitgeteilt bekommen. • Intuition: Name des Konstruktors Ki benutzen. 2. Gleichheit auf den Elementen des Typs ist nicht definiert. • Intuition: nur Werte die durch denselben Konstruktor Ki mit identischen Parametern erzeugt wurden, sind gleich. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 263 9 · Algebraische Datentypen � Deklaration eines algebraischen Datentyps · 9.1 Haskell kann diese Intuitionen automatisch zur Verfügung stellen, wenn die data-Deklaration durch den Zusatz deriving (Show, Eq) erweitert wird. 1 2 data Weekday = Mon | Tue | Wed | Thu | Fri | Sat | Sun deriving (Show, Eq) � Der neue Typ T wird damit automatisch Instanz der Typklasse Show aller druckbaren Typen und Instanz der Typklasse Eq aller Typen mit Gleichheit (==). � Der deriving-Mechanismus ist genereller und wird später noch genauer besprochen. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 264 9 · Algebraische Datentypen Deklaration eines algebraischen Datentyps · 9.1 Maybe an Integer? Algebraische Datentypen erlauben die Erweiterung eines Typs um einen speziellen Wert, der eingesetzt werden kann, wenn Berechnungen kein sinnvolles oder ein unbekanntes Ergebnis besitzen. Beispiel 1 2 3 Erweitere den Typ Integer um einen “Fehlerwert” None: data MaybeInt = Val Integer | None deriving (Show, Eq) 4 5 6 7 safediv :: Integer -> Integer -> MaybeInt safediv _ 0 = None safediv x y = Val (x ‘div‘ y) Fragen � Was sind in diesem Beispiel Konstruktorfunktionen? � Wie lautet jeweils ihr Typ? Stefan Klinger · DBIS Informatik 2 · Sommer 2016 265 9 · Algebraische Datentypen Deklaration eines algebraischen Datentyps · 9.1 Maybe α � Der vordefinierte Typkonstruktor Maybe kann jeden Typ um das Element Nothing erweitern. data Maybe α = Just α | Nothing deriving (Eq, Show) � Der Typkonstruktor ist polymorph (wie etwa auch [α]): Beispiel Erweitere den Typ Integer um einen “Fehlerwert” Nothing: 1 2 3 safediv :: Integer -> Integer -> Maybe Integer safediv _ 0 = Nothing safediv x y = Just (x ‘div‘ y) Fragen � Welchen Typ konstruiert der Typkonstruktor? Woraus? � Was sind die Typen der Konstruktorfunktionen? Stefan Klinger · DBIS Informatik 2 · Sommer 2016 266