3. Pattern Matching und verzögerte Auswertung
Pattern Matching ist eine der zentralen Aufgaben von Haskell, da es für die Auswahl der zur konkreten Situation passenden Definition notwendig ist. Warum dies so ist und wie dies geschieht soll in diesem Abschnitt gezeigt werden. Außerdem werden kurz einige Aspekte von "Lazy Evaluation" zusammengestellt und "Lazy Patterns" vorgestellt.
3.1 Grundlagen des Pattern Matching
Bis hier wurden bereits einige Deklarationen verwendet, zu deren Verwendung Pattern Matching notwendig ist. Darunter waren z.B.:
length [] = 0 length (y:ys) = 1+length ys fringe Leaf x = [x]So muss bei der Benutzung von
length unterschieden werden, ob die angegebene
Liste leer ist (in diesem Fall trifft die erste Definition zu) oder nicht (dann wird die
zweite Definition verwendet). Ist ein Match erfolgreich, d.h. entspricht das konkret
gegebene Muster den Anforderungen in der Definition, werden als "Seiteneffekt" die
"formalen Parameter" (im Fall von fringe ist das x) an die konkreten Werte
gebunden.
Man unterscheidet Patterns, die immer zu einem Match führen ("irrefutable patterns")
und solche, bei denen das nicht der Fall ist ("refutable patterns"). In die erste
Kategorie fallen z.B. formale Parameter von Funktionen wie add, in die zweite die
drei oben angegebenen Beispiele.
Pattern Matching kann grundsätzlich zu drei Ergebnissen führen:
- Erfolg ("succeed"). In diesem Fall werden die formalen Parameter gebunden und die Sache ist erledigt.
- Misserfolg ("fail"). Das Muster passt nicht. Es wird mit der nächsten verfügbaren Definition fortgefahren.
- Fehler ("diverge"). Bei der Auswertung eines aktuellen Parameters kam es zu einem Fehler. In diesem Fall wird das Programm abgebrochen.
bot ein nicht wohldefinierter Ausdruck. Versucht man, (1,2)
gegen (0,bot) zu matchen, führt dies zu einem fail, da die ersten Elemente der
Tupel nicht übereinstimmen und der Match-Vorgang folglich mit einem Misserfolg
abgebrochen wird. Matcht man (1,2) hingegen gegen (bot,0), so erhält man
Divergenz, da beim Versuch, bot zu vergleichen, letzteres nicht bestimmt
werden kann.
3.2 As-Patterns und Wildcards
Bei As-Patterns und Wildcards handelt es sich um zwei Typen von Patterns, die immer zu einem Match führen.
As-Patterns dienen dazu, einem bestimmten Teil eines Patterns einen eigenen Namen zuzuordnen. Als Beispiel betrachte man folgende Funktionsdeklaration.
f (y:ys) = y:y:ysUm die rechte Seite der Definition übersichtlicher zu machen, kann man ein As- Pattern benutzen.
f s@(y:ys) = y:sMittels
@ wird dem als Struktur (y:ys) angegebenen Parameter ein eigener Name
zugewiesen. Auch wenn (y:ys) als Pattern refutable ist, führt das As-Pattern formal
immer zu einem Match.
Wildcards _ dienen dazu, auf das Binden eines formalen Parameters zu verzichten,
wenn der entsprechende Teil eines Patterns nicht mehr benötigt wird. Sie können
einerseits dazu verwendet werden, die Irrelevanz eines Teils des Patterns zu
betonen. Andererseits führen sie aber auch dazu, dass die aktuellen Parameter an
dieser Stelle nicht ausgewertet werden, wodurch diese auch nicht zu einem fail des
Match beitragen können. Mit Hilfe einer Wildcard lässt sich die Definition der
Funktion head auch so formulieren:
head (y:_) = y
3.3 Lazy Evaluation und Lazy Patterns
Bisher wurde auf verzögerte Auswertung oder Lazy Evaluation nicht explizit eingegangen. Dieses Konzept meint im Grunde nichts anderes, als dass Ausdrücke erst bei Bedarf ausgewertet, Datenstrukturen erst bei ihrer Verwendung angelegt werden. Die reine Definition etwa einer langen Liste bewirkt also noch keinen Speicherverbrauch.
Hier sollen nun exemplarisch drei für das Programmdesign wichtige Aspekte dieses Konzepts rekapituliert werden, bevor mit Lazy Patterns eine mit Pattern Matching zusammenhängende Spielart von Haskells "Laziness" vorgestellt wird.
- Ausdrücke werden nur ausgewertet, wenn nötig. Definiert man z.B. eine
einstellige konstante Funktion
const13 a = 13
so ist diese auch definiert, wenn es der übergebene Parameter nicht ist.const13 1/0 ⇒ 13
- Pattern Matching bricht in dem Moment ab, wo eine Nichtübereinstimmung
gefunden wurde.
(1,2,3)==(0,1/0,3) ⇒ False (1,2,3)==(1,1/0,3) ⇒ ⊥
Hierbei steht ⊥ ("bottom") für einen Fehler. - Es ist möglich, unendliche Strukturen zu definieren. Definiert man sich z.B.
eine Liste ganzer Zahlen
intlist i = i:intlist (i+1)
so kann man mit der Funktiontake, die die ersten n Elemente einer Liste zurückliefert, die so konstruierte unendliche Folge tatsächlich benutzen.take 5 (intlist 1) ⇒ [1,2,3,4,5]
Man betrachte ein System bestehend aus einem Client und einem Server. Der Client
sendet Anfragen an den Server, der Server antwortet darauf. Die erste Anfrage wird
dem Client vorgegeben (init), alle weiteren hängen von der jeweils letzten Antwort
des Servers ab. Dies lässt sich in Haskell so formulieren.
reqs = client init resps resps = server reqs client init (resp:resps) = init : client (next resp) resps server (req:reqs) = process req : server reqsDabei müssen die Funktionen
next und process natürlich geeignet definiert sein.
Das offensichtliche Problem ist, dass beim ersten Aufruf von client der Server
noch keine Antworten erzeugt haben kann, dass also (resp:resps) nicht zu einem
Match führen kann. Dieses Problem lässt sich nun durch Lazy Patterns überwinden.
durch ein Tilde ~ wird das entsprechende Pattern als lazy gekennzeichnet, was dazu
führt, dass es unmittelbar zu einem Match führt und erst bei Verwendung überprüft
wird.
client init ~(resp:resps) = init : client (next resp) respsDamit erhält
reqs sein erstes Element, die Kommunikation wird wie zu erwarten
angestoßen und Client und Server Tauschen bis in alle Ewigkeit Nachrichten aus.
![]() | zurück | ![]() | Inhalt | ![]() | weiter |


