Programmieren in Haskell Stefan Janssen Programmieren in Haskell Pattern Matching Lazy Pattern Matching Client and server Lazy Patterns Stefan Janssen Universität Bielefeld AG Praktische Informatik 10. Dezember 2014 Themen-Vorschau Programmieren in Haskell Stefan Janssen Pattern Matching Client and server Pattern Matching: as-Patterns und lazy Patterns Client-Server-Programmierung Lazy Patterns Pattern Matching in Haskell Programmieren in Haskell Pattern matching (alias Mustervergleich) kennen wir bereits aus Funktionsgleichungen aus case-Ausdrücken Ein Pattern (Muster) ist ein Ausdruck bestehend aus Stefan Janssen Pattern Matching Client and server Lazy Patterns Variablen, der anonymen Variablen _ (“Joker”) Konstruktoren aller Datentypen Zahlen, Characters ... also keine Funktionen, die nicht Konstruktoren sind ... und keine Variable mehrfach, ausgenommen _ Ergonomie des Pattern Matching in Haskell Programmieren in Haskell Stefan Janssen Pattern matching leistet zweierlei Fallunterscheidung in übersichtlicher Form Nutzung der Reihenfolge bei überlappenden Mustern Bindung von Muster-Variablen an Substrukturen des geprüften Werts 1 2 3 Pattern Matching Client and server Lazy Patterns leaves ( Leaf a ) = [ a ] leaves ( Br ( Leaf a ) y ) = a : leaves y leaves ( Br ( Br x y ) z ) = leaves ( Br x ( Br y z )) As-Patterns Manchmal will man Muster verwenden und einen Namen für den (unzerlegten) Argumentwert haben Programmieren in Haskell suffixes ( x : xs ) = ( x : xs ): suffixes xs erzeugt eine überflüssige (:) Operation, vergeudet Zeit und verschwendet Platz. Besser mit as-pattern Pattern Matching Stefan Janssen 1 2 suffixes z@(x:xs) = z: suffixes xs Nun ist das schon vorhandene x:xs unter dem Namen z zu haben. Client and server Lazy Patterns As-Patterns Manchmal will man Muster verwenden und einen Namen für den (unzerlegten) Argumentwert haben Programmieren in Haskell suffixes ( x : xs ) = ( x : xs ): suffixes xs erzeugt eine überflüssige (:) Operation, vergeudet Zeit und verschwendet Platz. Besser mit as-pattern Pattern Matching Stefan Janssen 1 2 suffixes z@(x:xs) = z: suffixes xs Nun ist das schon vorhandene x:xs unter dem Namen z zu haben. Nebenbei: Welche Komplexität hat die Berechnung von suffixes x? Und welche die Ausgabe des Ergebnisses? Client and server Lazy Patterns Pattern Matching und Auswertungreihenfolge Programmieren in Haskell Stefan Janssen Vergleich eines Musters mit einem Wert erfordert, dass der Wert ausgerechnet wird, Pattern Matching Client and server Lazy Patterns aber nur soweit es für den Vergleich nötig ist (Laziness) Verwendung von Mustern hat also Auswirkung auf die Berechenungsreihenfolge Abweisbare und nicht abweisbare Muster Programmieren in Haskell a.k.a. refutable versus irrefutable patterns Ein Muster ohne Konstruktoren (also x, _ ) passt auf jedem Wert und heißt unabweisbar Ein Muster mit Konstruktoren heißt abweisbar 1 2 3 Ist eine Funktion mit unabweisbaren Mustern definiert, ist sie immer ein Redex. Beispiele: square x = x * x double f = f . f five _ = 5 NB: Man kann alle Funktionen auf der linken Seite mit unabweisbaren Mustern schreiben, wenn man stattdessen case-Ausdrücke auf der rechten Seite verwendet. Stefan Janssen Pattern Matching Client and server Lazy Patterns Pattern Matching und Terminierung (1) Erinnerung: Die Auswertungsstrategie in einer funktionalen Sprache hat keinen Einfluss auf den ggf. berechneten Wert, wohl aber auf die Terminierung der Berechnung. Ein Mustervergleich kann positive oder negativ ausgehen, oder divergieren Alle Muster in einer Gleichung werden top-down, links-rechts verglichen Geht ein Vergleich negativ aus, ist die aktuelle Gleichung nicht anwendbar, und es wird die nachfolgende Gleichung versucht Ruft der Vergleich eine nicht-endende Berechnung hervor, divergiert er und das Ergebnis der Gesamtrechnung ist undefiniert. Passen alle Muster einer Gleichung, wird sie angewendet Programmieren in Haskell Stefan Janssen Pattern Matching Client and server Lazy Patterns Bottom Programmieren in Haskell 1 Hier eine Definition des “undefinierten” Werts > bot = bot bot, ausgesprochen “bottom” ist der gänzlich undefinierte Wert. Seine Berechnung terminiert nicht, und produziert auch keinen Konstruktor. Stefan Janssen Pattern Matching Client and server Lazy Patterns Bottom Programmieren in Haskell 1 Hier eine Definition des “undefinierten” Werts > bot = bot bot, ausgesprochen “bottom” ist der gänzlich undefinierte Wert. Seine Berechnung terminiert nicht, und produziert auch keinen Konstruktor. Was wäre ein teilweise undefinierter Wert? Stefan Janssen Pattern Matching Client and server Lazy Patterns Bottom Programmieren in Haskell 1 Hier eine Definition des “undefinierten” Werts > bot = bot bot, ausgesprochen “bottom” ist der gänzlich undefinierte Wert. Seine Berechnung terminiert nicht, und produziert auch keinen Konstruktor. Was wäre ein teilweise undefinierter Wert? Nebenbei: Welchen Typ hat bot? Stefan Janssen Pattern Matching Client and server Lazy Patterns Pattern Matching und Terminierung (2) Programmieren in Haskell Stefan Janssen 1 2 3 1 2 3 Vergleiche take 0 _ = [] take _ [] = [] take n ( x : xs ) = x : take (n -1) xs und mit vertauschten Zeilen take ’ _ [] = [] take ’ 0 _ = [] take ’ n ( x : xs ) = x : take ’ (n -1) xs Einen Unterschied gibt es nur, wenn eines der Argumente undefiniert ist ... Pattern Matching Client and server Lazy Patterns Unterschiedliches Terminierungsverhalten Programmieren in Haskell Stefan Janssen 1 2 take 0 take ’ 0 bot bot = = > [] = = > undefiniert Pattern Matching = = > undefiniert = = > [] Lazy Patterns 3 4 5 take bot [] take ’ bot [] Der Haskell Prelude implementiert die Version take. Client and server Lazy Patterns Programmieren in Haskell a.k.a. Muster mit verzögertem Vergleich Problem: Man möchte eine Funktion mit einem Muster definieren und zwar für ein Argument, das durch die Anwendung der Funktion erst berechnet wird Zwar steht fest, dass das Muster erfüllt sein wird, jedoch zum Zeitpunkt der Funktionsaufrufs muss für den Mustervergleich die gleiche Funktion wieder aufgerufen werden ... Wo kommt so etwas überhaupt vor? Stefan Janssen Pattern Matching Client and server Lazy Patterns Client-Server-Anwendungen Client-Server-Anwendungen sind parallel laufende Programme, die über Ströme kommunizieren Nachrichten an einander – Requests und Responses – werden wechselseitig erzeugt und gelesen Sie werden in “Strömen” (streams) verwaltet. Ströme sind in Haskell einfach lazy Listen, in andern Sprachen weden sie als spezieller Datentyp zur Verfügung gestellt. Programmieren in Haskell Stefan Janssen Pattern Matching Client and server Lazy Patterns Zahlen raten als client-server Programm (1) Ratespiel: Finde eine Zahl n aus dem Intervall [0..N] durch Fragen der Art ”Ist n ≤ z?” mit geschickt gewählten Vergleichszahlen z. Programmieren in Haskell Stefan Janssen Pattern Matching Client and server Lazy Patterns Zahlen raten als client-server Programm (1) Ratespiel: Finde eine Zahl n aus dem Intervall [0..N] durch Fragen der Art ”Ist n ≤ z?” mit geschickt gewählten Vergleichszahlen z. 63 Programmieren in Haskell Stefan Janssen Pattern Matching Client and server Lazy Patterns Zahlen raten als client-server Programm (1) Ratespiel: Finde eine Zahl n aus dem Intervall [0..N] durch Fragen der Art ”Ist n ≤ z?” mit geschickt gewählten Vergleichszahlen z. Rollenverteilung: Client: Erzeugt Fragen und merkt sich das Ergebnis. Seine Strategie: Intervall-Halbierung; sie führt garantiert in O(log(N)) Schritten zum Ziel. Server: Kennt n und beantwortet Fragen ”Ist n ≤ z?” mit True oder False. Der Server antwortet wahrheitsgemäß. Kommunikation zwischen Client und Server über zwei Ströme: questions und answers. Programmieren in Haskell Stefan Janssen Pattern Matching Client and server Lazy Patterns Zahlen raten als client-server Programm (2) Programmieren in Haskell Eine Frage hat die Form (l,g,u) und bedeutet ausführlich: “Ich weiß dass n ∈ [l..u] und frage ob n ≤ g.” Stefan Janssen g ist dabei die Intervallmitte, und wird, je nach Antwort, für die nächste Frage ins linke oder rechte Halbintervall verlegt. Pattern Matching Client and server Lazy Patterns Das Ende erkennt der Client daran, dass das Intervall sich nicht mehr ändert. 1 2 3 4 5 > > > > > minN = 0 maxN = 1024 −− t a k e d i f f [ ] = [ ] −− t a k e d i f f [ x ] = [ x ] t a k e d i f f ( x : y : xs ) = x : i f y == x t h e n [ ] else t a k e d i f f ( y : xs ) takediff nimmt den Präfix einer Liste bis zur ersten Wiederholung Zahlen raten als client-server Programm (2) Programmieren in Haskell Stefan Janssen Wir definieren die Prozesse ask und reply, die über die Listen questions und answers kommunizieren. 1 2 3 > guess n = t a k e d i f f ( map ( \ ( _ , g , _) −> g ) q u e s t i o n s ) where Client and > answers = reply questions server > r e p l y ( ( _ , g , _ ) : q s ) = ( n <= g ) : r e p l y q s Lazy Patterns 4 5 6 7 8 9 Pattern Matching > > > > > q u e s t i o n s = a s k i n i t i a l a n s w e r s where i n i t i a l = ( minN , ( maxN + minN ) ‘ d i v ‘ 2 , maxN ) a s k ( l , g , u ) ( a : a s ) = ( l , g , u ) : a s k n e w g u e s s a s where newguess = i f a then ( l , (l + g ) ‘ div ‘ 2 , g ) e l s e ( g +1 , ( g+1 + u ) ‘ d i v ‘ 2 , u ) Zahlen raten als client-server Programm (2) Programmieren in Haskell Stefan Janssen Wir definieren die Prozesse ask und reply, die über die Listen questions und answers kommunizieren. 1 2 3 > guess n = t a k e d i f f ( map ( \ ( _ , g , _) −> g ) q u e s t i o n s ) where Client and > answers = reply questions server > r e p l y ( ( _ , g , _ ) : q s ) = ( n <= g ) : r e p l y q s Lazy Patterns 4 5 6 7 8 9 Pattern Matching > > > > > q u e s t i o n s = a s k i n i t i a l a n s w e r s where i n i t i a l = ( minN , ( maxN + minN ) ‘ d i v ‘ 2 , maxN ) a s k ( l , g , u ) ( a : a s ) = ( l , g , u ) : a s k n e w g u e s s a s where newguess = i f a then ( l , (l + g ) ‘ div ‘ 2 , g ) e l s e ( g +1 , ( g+1 + u ) ‘ d i v ‘ 2 , u ) Diese Version funktioniert nicht – warum? Verklemmung! Programmieren in Haskell Der Aufruf von ask (l,g,u) (a:as) stellt eine Frage ((l,g,u)), die von reply beantwortet wird (a) fragt jedoch in seinem rechten Muster bereits nach dieser Antwort ((a:as)), um aus ihr die nächste Frage (newguess) zu generieren Das pattern matching beim Aufruf von ask kommt zu frueh: es verlangt ein Ergbnis von reply, bevor der gegebene Aufruf von ask das erste Element von questions generiert hat. reply seinerseits verlangt ein Ergebnis von ask ... Stefan Janssen Pattern Matching Client and server Lazy Patterns Zahlen raten als client-server Programm (3) Vermeidung des übereifrigen Musters: 1 2 3 6 7 8 9 10 Stefan > guess ’ n = t a k e d i f f ( map ( \ ( _ , g , _) −> g ) q u e s t i o n s ) whereJanssen > answers = reply questions > r e p l y ( (_, g ,_ ) : qs ) = ( n <= g ) : r e p l y q s Pattern Matching 4 5 Programmieren in Haskell > > > > > > q u e s t i o n s = a s k i n i t i a l a n s w e r s where i n i t i a l = ( minN , ( maxN + minN ) ‘ d i v ‘ 2 , maxN ) a s k ( l , g , u ) a s = ( l , g , u ) : a s k n e w g u e s s ( t a i l a s ) where a = head a s newguess = i f a then ( l , (l + g ) ‘ div ‘ 2 , g ) e l s e ( g +1 , ( g+1 + u ) ‘ d i v ‘ 2 , u ) ask kann nun starten, ohne zuerst nach der Zerlegung der answers-Liste as zu fragen. Also kann die Frage ausgegeben und daraus die erste Antwort erzeugt werden, bevor mit head as nach ihr gefragt wird Client and server Lazy Patterns Lazy Patterns Programmieren in Haskell Stefan Janssen Pattern Matching Durch Client and server ˜(...) wird ein Pattern als lazy markiert. Das löst unser Verklemmungsproblem. Lazy Patterns Zahlen raten als client-server Programm (4) Programmieren in Haskell Der gleiche Effekt mit lazy pattern ~(a:as) 1 2 3 4 5 6 7 8 9 Stefan Janssen > guess ’ ’ n = t a k e d i f f ( map ( \ ( _ , g , _) −> g ) q u e s t i o n s ) where > answers = reply questions Pattern Matching > r e p l y ( ( _ , g , _ ) : q s ) = ( n <= g ) : r e p l y q s Client and > > > > > server q u e s t i o n s = a s k i n i t i a l a n s w e r s where i n i t i a l = ( minN , ( maxN + minN ) ‘ d i v ‘ 2 , maxN ) Lazy Patterns a s k ( l , g , u ) ~ ( a : a s ) = ( l , g , u ) : a s k n e w g u e s s a s where newguess = i f a then ( l , (l + g ) ‘ div ‘ 2 , g ) e l s e ( g +1 , ( g+1 + u ) ‘ d i v ‘ 2 , u ) der lazy pattern ~(a:as) wird beim Aufruf als erfüllt angenommen erst wenn a oder as gebraucht wird, findet der Abgleich statt. Lazy Patterns Programmieren in Haskell Stefan Janssen Lazy patterns, ~(...) sind unabweisbar. Ihre Überprüfung findet nur statt, wenn eine Komponente daraus gebraucht wird Scheitert sie, bricht die Rechnung ab. Eine weitere Mustergleichung nach einer Gleichung mit einem lazy Pattern macht also keinen Sinn. Pattern Matching Client and server Lazy Patterns Top level pattern binding: immer lazy Programmieren Werden Muster nicht auf Argumentposition, sondern in der in Haskell Definition von Konstanten benutzt, sind sie automatisch lazy: Stefan Janssen fibp@(1:tfibp) = 1:1:[a+b | (a,b) <- zip fibp tfibp ] Pattern Matching Client and server Lazy Patterns Top level pattern binding: immer lazy Programmieren Werden Muster nicht auf Argumentposition, sondern in der in Haskell Definition von Konstanten benutzt, sind sie automatisch lazy: Stefan Janssen fibp@(1:tfibp) = 1:1:[a+b | (a,b) <- zip fibp tfibp ] Hier haben wir einen as-Pattern, der eine konstante, unendliche Liste definiert, ... ... die Liste der Fibonacci-Zahlen. Das Muster bindet die Gesamtliste an den Namen fibp, zugleich ihren tail an den Namen tfibp die linke Seite fragt, ob die Liste nicht leer ist, aber dank der laziness des Musters wird dies erst geprüft, wenn tfibp rechts benutzt wird Pattern Matching Client and server Lazy Patterns Top level pattern binding: immer lazy Programmieren Werden Muster nicht auf Argumentposition, sondern in der in Haskell Definition von Konstanten benutzt, sind sie automatisch lazy: Stefan Janssen fibp@(1:tfibp) = 1:1:[a+b | (a,b) <- zip fibp tfibp ] Hier haben wir einen as-Pattern, der eine konstante, unendliche Liste definiert, ... ... die Liste der Fibonacci-Zahlen. Das Muster bindet die Gesamtliste an den Namen fibp, zugleich ihren tail an den Namen tfibp die linke Seite fragt, ob die Liste nicht leer ist, aber dank der laziness des Musters wird dies erst geprüft, wenn tfibp rechts benutzt wird Vergleiche: let a@(1:as) = 1:[] in 2:a let a@(0:as) = 1:[] in 2:a Pattern Matching Client and server Lazy Patterns