Lazy Lazy s Universität Bielefeld AG Praktische Informatik 10. Dezember 2014
Themen-Vorschau : as-s und lazy s Client-Server-Programmierung Lazy s
matching (alias Mustervergleich) kennen wir bereits aus Funktionsgleichungen aus case-ausdrücken Ein (Muster) ist ein Ausdruck bestehend aus Variablen, der anonymen Variablen _ ( Joker ) Konstruktoren aller Datentypen Zahlen, Characters... also keine Funktionen, die nicht Konstruktoren sind... und keine Variable mehrfach, ausgenommen _ Lazy s
Ergonomie des matching leistet zweierlei Fallunterscheidung in übersichtlicher Form Nutzung der Reihenfolge bei überlappenden Mustern Bindung von Muster-Variablen an Substrukturen des geprüften Werts 1 leaves ( Leaf a) = [a] 2 leaves ( Br ( Leaf a) y) = a : leaves y 3 leaves (Br (Br x y) z) = leaves (Br x (Br y z)) Lazy s
As-s 1 Manchmal will man Muster verwenden und einen Namen für den (unzerlegten) Argumentwert haben 2 suffixes (x:xs) = (x:xs ): suffixes xs erzeugt eine überflüssige (:) Operation, vergeudet Zeit und verschwendet Platz. Besser mit as-pattern Lazy s suffixes z@(x:xs) = z: suffixes xs Nun ist das schon vorhandene x:xs unter dem Namen z zu haben.
As-s 1 Manchmal will man Muster verwenden und einen Namen für den (unzerlegten) Argumentwert haben 2 suffixes (x:xs) = (x:xs ): suffixes xs erzeugt eine überflüssige (:) Operation, vergeudet Zeit und verschwendet Platz. Besser mit as-pattern Lazy s 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?
und Auswertungreihenfolge Vergleich eines Musters mit einem Wert erfordert, dass der Wert ausgerechnet wird, aber nur soweit es für den Vergleich nötig ist (Laziness) Verwendung von Mustern hat also Auswirkung auf die Berechenungsreihenfolge Lazy s
Abweisbare und nicht abweisbare Muster 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 Ist eine Funktion mit unabweisbaren Mustern definiert, ist sie immer ein Redex. Beispiele: 1 square x = x * x 2 double f = f. f 3 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. Lazy s
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 Lazy s
Bottom Hier eine Definition des undefinierten Werts 1 > bot = bot bot, ausgesprochen bottom ist der gänzlich undefinierte Wert. Seine Berechnung terminiert nicht, und produziert auch keinen Konstruktor. Lazy s
Bottom Hier eine Definition des undefinierten Werts 1 > bot = bot bot, ausgesprochen bottom ist der gänzlich undefinierte Wert. Seine Berechnung terminiert nicht, und produziert auch keinen Konstruktor. Lazy s Was wäre ein teilweise undefinierter Wert?
Bottom Hier eine Definition des undefinierten Werts 1 > bot = bot bot, ausgesprochen bottom ist der gänzlich undefinierte Wert. Seine Berechnung terminiert nicht, und produziert auch keinen Konstruktor. Lazy s Was wäre ein teilweise undefinierter Wert? Nebenbei: Welchen Typ hat bot?
und Terminierung (2) Vergleiche 1 take 0 _ = [] 2 take _ [] = [] 3 take n (x:xs) = x : take (n -1) xs und mit vertauschten Zeilen 1 take _ [] = [] 2 take 0 _ = [] 3 take n (x:xs) = x : take (n -1) xs Einen Unterschied gibt es nur, wenn eines der Argumente undefiniert ist... Lazy s
Unterschiedliches Terminierungsverhalten 1 take 0 bot ==> [] 2 take 0 bot = = > undefiniert 3 4 take bot [] = = > undefiniert 5 take bot [] ==> [] Lazy s Der Haskell Prelude implementiert die Version take.
Lazy s 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... Lazy s Wo kommt so etwas überhaupt vor?
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 einfach lazy Listen, in andern Sprachen weden sie als spezieller Datentyp zur Verfügung gestellt. Lazy s
Zahlen raten als client- 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. Lazy s
Zahlen raten als client- 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 Lazy s
Zahlen raten als client- 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. Lazy s
Zahlen raten als client- Programm (2) Eine Frage hat die Form (l,g,u) und bedeutet ausführlich: Ich weiß dass n [l..u] und frage ob n g. g ist dabei die Intervallmitte, und wird, je nach Antwort, für die nächste Frage ins linke oder rechte Halbintervall verlegt. Das Ende erkennt der Client daran, dass das Intervall sich nicht mehr ändert. 1 > minn = 0 2 > maxn = 1024 3 > t a k e d i f f [ ] = [ ] 4 > t a k e d i f f [ x ] = [ x ] 5 > t a k e d i f f ( x : y : x s ) = x : i f y == x then [ ] e l s e t a k e d i f f ( y : x s ) takediff nimmt den Präfix einer Liste bis zur ersten Wiederholung Lazy s
Zahlen raten als client- Programm (2) Wir definieren die Prozesse ask und reply, die über die Listen questions und answers kommunizieren. 1 > g u e s s n = t a k e d i f f (map ( \ (_, g, _) > g ) q u e s t i o n s ) where 2 > a n s w e r s = r e p l y q u e s t i o n s 3 > r e p l y ( ( _, g, _ ) : qs ) = ( n <= g ) : r e p l y qs 4 5 > q u e s t i o n s = ask i n i t i a l a n s w e r s where 6 > i n i t i a l = ( minn, (maxn + minn ) div 2, maxn) 7 > ask ( l, g, u ) ( a : as ) = ( l, g, u ) : ask newguess as where 8 > newguess = i f a then ( l, ( l + g ) div 2, g ) 9 > e l s e ( g+1, ( g+1 + u ) div 2, u ) Lazy s
Zahlen raten als client- Programm (2) Wir definieren die Prozesse ask und reply, die über die Listen questions und answers kommunizieren. 1 > g u e s s n = t a k e d i f f (map ( \ (_, g, _) > g ) q u e s t i o n s ) where 2 > a n s w e r s = r e p l y q u e s t i o n s 3 > r e p l y ( ( _, g, _ ) : qs ) = ( n <= g ) : r e p l y qs 4 5 > q u e s t i o n s = ask i n i t i a l a n s w e r s where 6 > i n i t i a l = ( minn, (maxn + minn ) div 2, maxn) 7 > ask ( l, g, u ) ( a : as ) = ( l, g, u ) : ask newguess as where 8 > newguess = i f a then ( l, ( l + g ) div 2, g ) 9 > e l s e ( g+1, ( g+1 + u ) div 2, u ) Diese Version funktioniert nicht warum? Lazy s
Verklemmung! 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... Lazy s
Zahlen raten als client- Programm (3) Vermeidung des übereifrigen Musters: 1 > guess n = t a k e d i f f (map ( \ (_, g, _) > g ) q u e s t i o n s ) where 2 > a n s w e r s = r e p l y q u e s t i o n s 3 > 4 r e p l y ( ( _, g, _ ) : qs ) = ( n <= g ) : r e p l y qs 5 > q u e s t i o n s = ask i n i t i a l a n s w e r s where 6 > i n i t i a l = ( minn, (maxn + minn ) div 2, maxn) 7 > ask ( l, g, u ) as = ( l, g, u ) : ask newguess ( t a i l as ) where Lazy s 8 > a = head as 9 > newguess = i f a then ( l, ( l + g ) div 2, g ) 10 > e l s e ( g+1, ( g+1 + u ) div 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
Lazy s Durch (...) Lazy s wird ein als lazy markiert. Das löst unser Verklemmungsproblem.
Zahlen raten als client- Programm (4) Der gleiche Effekt mit lazy pattern ~(a:as) 1 > guess n = t a k e d i f f (map ( \ (_, g, _) > g ) q u e s t i o n s ) where 2 > a n s w e r s = r e p l y q u e s t i o n s 3 > r e p l y ( ( _, g, _ ) : qs ) = ( n <= g ) : r e p l y qs 4 5 > q u e s t i o n s = ask i n i t i a l a n s w e r s where 6 > i n i t i a l = ( minn, (maxn + minn ) div 2, maxn) 7 > ask ( l, g, u ) ~( a : as ) = ( l, g, u ) : ask newguess as where Lazy s 8 > newguess = i f a then ( l, ( l + g ) div 2, g ) 9 > e l s e ( g+1, ( g+1 + u ) div 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 s 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 macht also keinen Sinn. Lazy s
Top level pattern binding: immer lazy Werden Muster nicht auf Argumentposition, sondern in der Definition von Konstanten benutzt, sind sie automatisch lazy: fibp@(1:tfibp) = 1:1:[a+b (a,b) <- zip fibp tfibp ] Lazy s
Top level pattern binding: immer lazy Werden Muster nicht auf Argumentposition, sondern in der Definition von Konstanten benutzt, sind sie automatisch lazy: fibp@(1:tfibp) = 1:1:[a+b (a,b) <- zip fibp tfibp ] Hier haben wir einen as-, 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 Lazy s
Top level pattern binding: immer lazy Werden Muster nicht auf Argumentposition, sondern in der Definition von Konstanten benutzt, sind sie automatisch lazy: fibp@(1:tfibp) = 1:1:[a+b (a,b) <- zip fibp tfibp ] Hier haben wir einen as-, 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 Lazy s