4 · Haskell – Typen, Werte und einfache Definitionen 4.8 � � Tupel · 4.8 Tupel Tupel erlauben die Gruppierung von Werten unterschiedlicher Typen (im Gegensatz zu Listen, welche immer homogen sind). Ein Tupel (c1 ,c2 ,...,cn ) besteht aus einer fixen Anzahl von Komponenten ci :: αi . Der Typ dieses Tupels wird notiert als (α1 ,α2 ,...,αn ). Beispiele (1, ’a’) ("foo", True, 2) ([(*1), (+1)], [1..10]) ((1,’a’),True) � :: :: :: :: (Integer, Char) ([Char], Bool, Integer) ([Integer -> Integer], [Integer]) ((Integer, Char), Bool) Die Position einer Komponente in einem Tupel ist signifikant. Es gilt (c1 ,c2 ,...,cn ) = (d1 ,d2 ,...,dm ) genau dann, wenn n = m und ∀i; 1 ≤ i ≤ n. ci = di . Stefan Klinger · DBIS Informatik 2 · Sommer 2016 146 4 · Haskell – Typen, Werte und einfache Definitionen Tupel · 4.8 Zugriff auf Tupel-Komponenten � Der Zugriff auf die einzelnen Komponenten eines Tupels geschieht durch Pattern Matching, cf. Seite 150. Beispiel Zugriffsfunktionen für die Komponenten eines 2-Tupels und Tupel als Funktionsergebnis: 1 2 fst :: (α, β) -> α fst (x,y) = x 3 4 5 snd :: (α, β) -> β snd (x,y) = y 6 7 8 mult :: Integer -> (Integer, Integer -> Integer) mult x = (x, (*x)) 9 10 11 > snd (mult 3) 5 15 Stefan Klinger · DBIS Informatik 2 · Sommer 2016 147 5 Funktionsdefinitionen 5 · Funktionsdefinitionen Typischerweise analysieren Funktionen ihre Argumente, um Fallunterscheidungen für die Berechnung des Funktionsergebnisses zu treffen. Je nach Beschaffenheit des Argumentes wird ein bestimmter Berechnungszweig gewählt. Beispiel 1 Summiere die Elemente einer Liste l: sum :: [Integer] -> Integer 2 3 4 5 sum xs = if xs == [] then 0 else head xs + sum (tail xs) --- ○ 1 ○ 2 sum hat den Berechnungszweig mittels if · then · else im Fall der leeren 1 bzw. nichtleeren Liste ○ 2 auszuwählen und im letzeren Fall explizit Liste ○ auf Kopf und Restliste von l zuzugreifen. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 149 5 · Funktionsdefinitionen 5.1 � � Pattern Matching und case · 5.1 Pattern Matching und case Pattern Matching erlaubt, für jeden Berechnungszweig einer Funktion f Muster pi1 , ..., pik für die erwartete Struktur der Argumente anzugeben. Wenn die Argumente von f den Mustern pi1 , ..., pik entsprechen, wird der entsprechende Berechnungszweig ei ausgewählt: f :: α1 -> ... -> αk -> β f p11 ... p1k = e1 f p21 ... p2k = e2 .. . f pn1 ... pnk = en Die ei müssen dabei einen gemeinsamen (allgemeinsten) Typ β besitzen. Semantik Beim Aufruf f x1 ... xk ... � werden die xj gegen die Muster pij (i = 1...n, von oben nach unten) “gematcht” und der erste Berechnungszweig gewählt, bei dem ein vollständiger Match vorliegt. � Wenn kein Match erzielt wird, bricht das Programm ab. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 150 5 · Funktionsdefinitionen Pattern Matching und case · 5.1 Definition Erlaubte Formen für ein Pattern pij � Variable v — Der Match gelingt immer; v wird an das aktuelle Argument xj gebunden und ist in ei verfügbar; Pattern müssen linear sein, d.h. eine Variable v darf nur einmal in einem Pattern auftauchen. � Konstante c — Der Match gelingt nur mit einem Argument xj welches zu c reduziert, d.h. xj � c. � Wildcard ‘_’ — Der Match gelingt immer, es wird aber keine Variablenbindung hergestellt. (don’t care) � Tupel-Pattern (p1 ,p2 ,...,pm ) — Der Match gelingt mit einem m-Tupel, dessen Komponenten mit den Pattern p1 , ..., pm matchen. � List-Pattern [] und (p:ps) — Während [] nur auf die leere Liste matcht, gelingt der Match mit (p:ps) für jede nichtleere Liste, deren Kopf das Pattern p und deren Rest das Pattern ps matcht. � Diese Definition ist aufgrund der beiden letzten Fälle rekursiv. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 151 5 · Funktionsdefinitionen Pattern Matching und case · 5.1 Beispiel Funktion sum mittels Pattern Matching: 1 sum :: [Integer] -> Integer 2 3 4 sum [] = 0 sum (x:xs) = x + sum xs Sowohl die Fallunterscheidung als auch der Zugriff auf head und tail des Arguments geschehen nun elegant durch Pattern Matching. Beispiel 1 Funktion zur Bestimmung der ersten n Elemente einer Liste: take :: Int -> [α] -> [α] 2 3 4 5 take 0 _ = [] take _ [] = [] take n (x:xs) = x : take (n-1) xs Stefan Klinger · DBIS Informatik 2 · Sommer 2016 152 5 · Funktionsdefinitionen Pattern Matching und case · 5.1 Beispiel Berechne xn (kombiniert Wildcard und Tupel-Pattern): 1 power :: (Float, Integer) -> Float 2 3 4 power (_, 0) = 1.0 power (x, n) = x * power (x,n-1) Beachte: power ist trotz der Übergabe von x und n eine Funktion eines (Nur zur Demonstration von Tupel-Pattern) (tupelwertigen) Parameters. � 1 Vorsicht Eine potentielle Fehlerquelle sind Definitionen wie diese: foo :: (Integer,Integer) -> Integer 2 3 4 foo (x, y) = x + y foo (0, _) = 0 --- ○ 1 ○ 2 2 wird auch bei einem Aufruf foo (0,5) nicht ausgewertet Der Zweig ○ 1 überdeckt das Pattern in Zweig ○ 2 ). (das Pattern in Zweig ○ Allgemein gilt: Spezialfälle vor den allgemeineren Fällen anordnen. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 153 5 · Funktionsdefinitionen Pattern Matching und case · 5.1 Layered Patterns � � Auf die Komponenten eines Wertes e kann mittels Pattern Matching zugegriffen werden. Oft ist in Funktionsdefinitionen aber gleichzeitig auch der Wert von e selbst interessant. Sei v eine Variable, p ein Pattern. Das Layered Pattern (as-Pattern) v @p matcht gegen e, wenn p gegen e matcht. Zusätzlich wird v an den Wert e gebunden. Beispiel Variante der Funktion within (cf. Seite 80). Schneide Liste von Näherungswerten ab, sobald sich die Werte weniger als eps unterscheiden. 1 2 3 4 5 6 within’ within’ within’ within’ :: Float -> [Float] -> [Float] _ [] = [] _ [y] = [y] eps (y:rest@(x:_)) = if abs (x-y) < eps then [y,x] else y : within’ eps rest Stefan Klinger · DBIS Informatik 2 · Sommer 2016 154 5 · Funktionsdefinitionen Pattern Matching und case · 5.1 Nützlichkeit von Layered Patterns Aufgabe: Mische 2 bezüglich lt geordnete Listen (z.B. in der merge-Phase von Mergesort): merge (<) [1,3 .. 10] [2,4 .. 10] Beispiel 1 Formulierung ohne as-Patterns � [1,2,3, ..., 10] merge :: (α -> α -> Bool) -> [α] -> [α] -> [α] 2 3 4 5 6 7 merge lt [] ys = ys merge lt xs [] = xs merge lt (x:xs) (y:ys) = if x ‘lt‘ y then x : merge lt xs (y:ys) else y : merge lt (x:xs) ys Die Listenargumente werden erst mittels Pattern Matching analysiert, um danach evtl. wieder via (:) identisch zusammengesetzt zu werden. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 155 5 · Funktionsdefinitionen Pattern Matching und case · 5.1 Beispiel Jetzt Formulierung mit as-Patterns: 1 merge :: (α -> α -> Bool) -> [α] -> [α] -> [α] 2 3 4 5 6 7 merge lt [] ys = ys merge lt xs [] = xs merge lt l1@(x:xs) l2@(y:ys) = if x ‘lt‘ y then x : merge lt xs l2 else y : merge lt l1 ys � Hier ist die Listenrekonstruktion nicht notwendig. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 156 5 · Funktionsdefinitionen Pattern Matching und case · 5.1 case-Ausdrücke � Pattern Matching ist so zentral, dass es nicht nur in Zweigen einer Funktionsdefinition, sondern auch als eigenständige Form zur Verfügung steht. case e of p1 p2 -> -> .. . e1 e2 pn -> en � Der Wert des Ausdrucks e wird nacheinander gegen die Pattern pi (i = 1...n) gematcht. � Falls der Match e auf pk gelingt, so ist der Wert des Gesamtausdrucks ek . Die Variablenbindungen aus pk stehen in ek zur Verfügung. � Trifft kein Muster zu, so bricht das Programm ab. (Der Wert des Ausdrucks ist dann ⊥ (bottom), s. später). Stefan Klinger · DBIS Informatik 2 · Sommer 2016 157 5 · Funktionsdefinitionen Pattern Matching und case · 5.1 Beispiel “Zipper” für zwei Listen. 1 zip’ :: [a] -> [b] -> [(a, b)] 2 3 4 5 zip’ [] _ = [] zip’ _ [] = [] zip’ (x:xs) (y:ys) = (x, y) : zip xs ys Die Fallunterscheidung könnte man genauso gut25 auch auf der rechten Seite der Funktionsdefinition treffen: 1 zip :: [α] -> [β] -> [(α,β)] 2 3 4 5 6 zip xs ys = case (xs, ys) of ([], _) -> [] (_, []) -> [] (x:xs, y:ys) -> (x, y) : zip xs ys Übrigens In Haskell gehören case-Ausdrücke zum innersten Sprachkern. Viele andere Sprachkonstrukte (insb. alle, die auf Pattern Matching bauen) werden intern auf case zurückgeführt. 25 was ist lesbarer? praktischer? Stefan Klinger · DBIS Informatik 2 · Sommer 2016 158 5 · Funktionsdefinitionen Pattern Matching und case · 5.1 Sprachkern? � Viele Programmiersprachen bieten verschiedene syntaktische Formen um das Gleiche auszudrücken. � Beim Bau eines Compilers könnte man für jede dieser Formen eigene Übersetzungsregeln definieren. � Mehr Arbeitsaufwand (Übersetzungsregeln sind oft aufwändig). � Drücken die unterschiedlichen Formen wirklich das Gleiche aus? Beweisen! � Üblicherweise wird eine essentielle Kernsprache (aka. core language) definiert, die nur die nötigsten Konstrukte der Sprache enthält. � Alle anderen Konstrukte (aka. syntactic sugar) können auf die Kernsprache zurückgeführt werden. � Ein Compiler für die Kernsprache ist leichter zu konstruieren. � Oft können Spracherweiterungen bequem als syntaktischer Zucker realisiert werden ⇒ Kein Eingriff in den Sprachkern nötig. Der Übergang zur Kernsprache ist typischerweise eine recht frühe Übersetzungsphase, cf. Seite 25. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 159 5 · Funktionsdefinitionen Pattern Matching und case · 5.1 case gehört zum Haskell Sprachkern — Beispiele � Bedingte Ausdrücke werden mittels case implementiert if e1 then e2 else e3 ≡ case e1 of True -> e2 False -> e3 • Damit wird die Forderung nach einem gemeinsamen allgemeinsten Typ α von e2 und e3 deutlich. � Funktionsdefinitionen mit Pattern Matching werden intern in case-Ausdrücke über Tupeln übersetzt: f p1 ... pk = e ≡ f v1 ... vk = case (v1 ,...,vk ) of (p1 ,...,pk ) -> e • Dabei sind v1 , ..., vk neue Variablen. • So hat der Compiler lediglich die etwas einfachere Aufgabe, Funktionsdefinitionen ohne Pattern Matching zu übersetzen. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 160 5 · Funktionsdefinitionen 5.2 � � Guards · 5.2 Guards Oft ist die Analyse der Struktur der Argumente einer Funktion nicht ausreichend, um den korrekten Berechnungszweig auszuwählen. Guards bieten die Möglichkeit, zusätzlich beliebige Tests auszuführen, wenn Zweige gewählt werden: f :: α1 -> ... -> αk f p11 ... p1k | g11 = | g12 = | ... = .. . f pn1 ... pnk | gn1 = | gn2 = | ... = � � -> β e11 e12 ... en1 en2 ... Die Guards gij sind Ausdrücke des Typs Bool. In den Guards gij (j ≥ 1) sind die durch die Pattern pi1 ...pik gebundenen Variablen nutzbar. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 161 5 · Funktionsdefinitionen Guards · 5.2 Semantik von Guards .. . f pi1 ...pik | gi1 | gi2 | ... .. . � = ei1 = ei2 = ... Falls die Pattern pi1 , ..., pik matchen, werden die Guards gij der Reihe nach von oben nach unten (j = 1, j = 2, . . .) getestet. • Der erste Guard gij der dabei zu True ausgewertet wird bestimmt eij als Ergebnis von f. • Wird keiner der Guards gik erfüllt, wird nach dem nächsten matchenden Pattern gesucht. • Der spezielle Guard otherwise evaluiert immer zu True, nützlich als Default-Alternative. � Guards können oft explizite Abfragen mittels if · then · else ersetzen. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 162 5 · Funktionsdefinitionen Beispiel 1 2 3 4 2 3 4 5 6 Selektiere die Elemente einer Liste, die die Bedingung p erfüllen. filter :: (α -> Bool) -> [α] -> [α] filter p [] = [] filter p (x:xs) | p x = x : filter p xs | otherwise = filter p xs Beispiel 1 Guards · 5.2 Noch eine Variante von within: within :: Float -> [Float] -> [Float] within _ [] = [] within _ [y] = [y] within eps (y:rest@(x:_)) | abs (x-y) < eps = [y,x] | otherwise = y : within eps rest Stefan Klinger · DBIS Informatik 2 · Sommer 2016 163 5 · Funktionsdefinitionen Beispiel 1 Guards · 5.2 Lösche adjazente Duplikate aus einer Liste. remdups :: [Integer] -> [Integer] 2 3 4 5 remdups (x:xs@(y:_)) | x == y = remdups xs | otherwise = x : remdups xs remdups xs = xs Frage: Könnte der letzte Zweig auch remdups [] = [] geschrieben werden? Beispiel Ist ein Element e in einer absteigend geordneten Liste vorhanden? 1 elem’ :: Integer -> [Integer] -> Bool 2 3 4 5 6 elem’ _ [] elem’ e (x:xs) | e > x | e == x | e < x Stefan Klinger · DBIS = = = = False False True elem’ e xs Informatik 2 · Sommer 2016 164 5 · Funktionsdefinitionen � Guards · 5.2 Guards können auch in case-Ausdrücken verwendet werden. Die Syntax wird analog zu der von Funktionsdefinitionen erweitert: case e of p1 | g11 | g12 pn | gn1 | gn2 Beispiel 1 -> -> .. . -> -> .. . e11 e12 en1 en2 Entferne die ersten n Elemente einer Liste. drop :: Integer -> [α] -> [α] 2 3 4 5 6 drop n xs = case xs of [] -> [] (x:xs) | n > 0 -> drop (n-1) xs | n == 0 -> x:xs Stefan Klinger · DBIS Informatik 2 · Sommer 2016 165 5 · Funktionsdefinitionen 5.3 Lokale Definitionen · 5.3 Lokale Definitionen Es kann oft nützlich sein, lediglich lokal sichtbare Namen in Ausdrücken zu verwenden. Dies dient � der Beschränkung der Sichtbarkeit von Namen (Verbergen von Implementationdetails), � dem “Herausfaktorisieren” öfter auftretender identischer Teilausdrücke aus einem Ausdruck (kann die Effizienz der Auswertung steigern). Vgl. lokale Definitionen/Deklarationen in blockstrukturierten (imperativen) Sprachen. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 166 5 · Funktionsdefinitionen Lokale Definitionen · 5.3 let-Ausdrücke Wenn e, e1 , . . . , en Haskell-Ausdrücke sind, und p1 , . . . , pn Patterns, so ist auch der let-Ausdruck rechts ein gültiger Haskell-Ausdruck. � � let p1 = e1 p 2 = e2 .. . p n = en in e Die Reihenfolge der Definitionen pi = ei ist unerheblich, sie dürfen wechselseitig rekursiv sein. In e erscheinen die durch den Pattern-Match von ei gegen pi definierten Namen an ihre Werte gebunden und sind außerhalb des Scopes von let unbekannt. Semantik den Wert Falls die ei nicht rekursiv sind (!) hat der obige let-Ausdruck (λ p1 p2 ... pn . e) e1 e2 ... en � � Die Auswertung der ei geschieht also lazy: (ei wird nur dann tatsächlich ausgewertet, wenn dies zur Auswertung von e erforderlich ist). Wir werden im Kapitel Typinferenz eine weitere Besonderheit sehen. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 167 5 · Funktionsdefinitionen Lokale Definitionen · 5.3 Beispiel � 1 Wir vervollständigen die Implementation von Mergesort (cf. Seite 155): mergesort :: (α -> α -> Bool) -> [α] -> [α] 2 3 4 5 6 mergesort lt [] = [] mergesort lt [x] = [x] mergesort lt xs = let (l1,l2) = split xs in merge lt (mergesort lt l1) (mergesort lt l2) � Es verbleibt die Definition der für Mergesort typischen Divide-Phase mittels split, die eine Liste xs in zwei Listen ungefähr gleicher Länge teilt (das Listenpaar wird in einem 2-Tupel zurückgegeben). � Frage: Wie ist eine Liste xs unbekannter Länge in zwei ca. gleich lange Teillisten l1 , l2 zu teilen? Stefan Klinger · DBIS Informatik 2 · Sommer 2016 168 5 · Funktionsdefinitionen 1 split, Version ○ 1 split Lokale Definitionen · 5.3 Teile in der Mitte (mit Funktion length). :: [α] -> ([α], [α]) 2 3 split xs = nsplit (length xs ‘div‘ 2) xs 4 5 6 nsplit :: Int -> [α] -> ([α], [α]) 7 8 nsplit n xs = (take n xs, drop n xs) Besser: Verstecke Implementationsdetail nsplit in einem let-Ausdruck 1 split :: [α] -> ([α], [α]) 2 3 4 split xs = let nsplit n xs = (take n xs, drop n xs) in nsplit (length xs ‘div‘ 2) xs Stefan Klinger · DBIS Informatik 2 · Sommer 2016 169 5 · Funktionsdefinitionen Lokale Definitionen · 5.3 1 : xs wird zweimal Offensichtlicher Nachteil der split Version ○ durchlaufen. 2 Durchlaufe xs nur einmal, füge dabei abwechselnd ein split, Version ○ Element in l1 oder l2 ein. 1 split2 :: [α] -> ([α], [α]) 2 3 4 5 6 split2 [] = ([], []) split2 [x] = ([x], []) split2 (x:x’:xs) = let (l1, l2) = split2 xs in (x:l1, x’:l2) Das Pattern (l1, l2) wird verwendet um das Ergebnis des rekursiven Aufrufs von split2 aufzutrennen. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 170 5 · Funktionsdefinitionen Lokale Definitionen · 5.3 where-Klauseln � Lokale Definitionen lassen sich alternativ mit einer where-Klausel einführen. � Sie erweitert die Syntax von Funktionsdefinitionen und case-Ausdrücke ein weiteres Mal. � Die dij sind jeweils in den Guards gik und Ausdrücken eik (k = 1...) des gesamten Definitionszweiges i sichtbar. Dies lässt sich mit let-Ausdrücken nicht formulieren. � Für die lokalen Definitionen dij gelten die zuvor bei let erklärten Vereinbarungen. Stefan Klinger · DBIS f :: α1 -> ... -> αk -> f p11 ... p1k | g11 = | g12 = |... = where d11 d12 .. . f pn1 ...pnk | gn1 | gn2 |... where Informatik 2 · Sommer 2016 = = = dn1 dn2 .. . β e11 e12 ... en1 en2 ... 171 5 · Funktionsdefinitionen � 1 2 3 4 Lokale Definitionen · 5.3 where-Klauseln sind in allen Guards und rechten Seiten eines Zweiges sichtbar... f x y | y > z | y == z | otherwise where z = x*x = ... z ... = ... z ... = ... z ... Beispiel Euklids Algorithmus zur Bestimmung des größten gemeinsamen Teilers (ggT): 1 ggT :: Integer -> Integer -> Integer 2 3 4 5 6 ggT x y = ggT’ (abs x) (abs y) where ggT’ x 0 = x ggT’ x y = ggT’ y (x ‘mod‘ y) � Mit let werden Ausdrücke konstruiert, dagegen gehört where zur Syntax der Funktionsdefinition bzw. zur Syntax von case-Ausdrücken. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 172 5 · Funktionsdefinitionen Beispiel 1 2 Lokale Definitionen · 5.3 Endgültige Mergesort-Implementierung mittels where und let. -- Divide-and-Conquer Sortierung einer Liste mergesort :: (α -> α -> Bool) -> [α] -> [α] 3 4 mergesort _ [] = [] 5 6 mergesort _ [x] = [x] 7 8 9 10 11 mergesort lt xs = let (l1,l2) = split xs in merge (mergesort lt l1) (mergesort lt l2) where 12 13 14 15 16 -- splitte eine split [] split [x] split (x:x’:xs) 17 Liste in zwei gleich lange Teile = ([],[]) = ([x],[]) = let (l1,l2) = split xs in (x:l1,x’:l2) 18 19 20 21 22 23 24 -- mische zwei sortierte Listen merge [] ys = ys merge xs [] = xs merge l1@(x:xs) l2@(y:ys) | x ‘lt‘ y = x : merge xs l2 | otherwise = y : merge l1 ys Stefan Klinger · DBIS Informatik 2 · Sommer 2016 173