Programmieren in Haskell

Werbung
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
Herunterladen