Pattern Matching in ML 1 Pattern Matching in ML Motivation ML ist eine funktionale Programmiersprache und insofern in manchen Dingen etwas unhandlich, wenn man in diesem Paradigma bleiben möchte. Inbesondere, wenn viele Fallentscheidungen anfallen, wird der Code schnell unübersichtlich; denkt nur an diese Funktion, die auf Aufgabenblatt 1 mathematisch formuliert war (und ohne Weiteres in ML umsetzbar ist): fun has_to_or_three ( l : ’a list ) : bool = if ( null(l) ) then false else if ( null(tl(l)) ) then false else if ( null(tl(tl(l))) ) then true else null(tl(tl(tl(l)))); Man stelle sich nun vor, wie das aussähe, wenn auf 10 Elemente zu prüfen wäre. Ein anderes kaum lesbares Beispiel liefert das Aufgabenblatt zwei, wo aus einer dreielementigen Liste die zwei größten zurückzugeben waren: fun two_biggest ( l : int list ) : int list = if hd(l) > hd(tl(l)) then if hd(tl(l)) > hd(tl(tl(l))) then [hd(l), hd(tl(l))] else [hd(l), hd(tl(tl(l)))] else if hd(l) > hd(tl(tl(l))) then [hd(l), hd(tl(l))] else [hd(tl(l)), hd(tl(tl(l)))]; Diese Beispiele sind so sicherlich in vielerlei Hinsicht ungeschickt implementiert; insbesondere wäre die zweite Aufgabe für allgemeine Listen wesentlich angenehmer zu lösen gewesen. Ich denke aber, wir sind uns einig, wenn ich sage, dass diese Funktionen nicht lesbar oder gar ästhetisch sind. Ich möchte daher im Folgenden das Pattern Matching vorstellen, das ein sehr elegantes Mittel liefert, um mit den nativen Datenstrukturen von ML umzugehen. Was ist denn das nun? Zunächst das Offensichtlichste: Pattern Matching ist Mustererkennung - was eine Überraschung! Dahinter steckt genau das: In Werten werden Muster erkannt. Dazu gibt es Folgende Werkzeuge: • Literale ( Zahlen für int, true/f alse für bool, Zeichen für char, [. . . ] für Listen . . . ) • Konstruktoren ( :: für Listen, (·) für Tupel . . . ) • Bezeichner Daraus könnt ihr nun Pattern zusammenbauen, im Folgenden einige Beispiele: 5 (true, 5) (x :: rl,b) x::y::z::[] _ Raphael Reitzig, 10. November 2007 Pattern Matching in ML 2 Worauf matchen diese Pattern nun? Gehen wir sie durch: • 5 Das Literal 5 matcht auf den Integerwert 5, welche Überraschung. • (true, 5) Hier matchen alle Tupel, die genau so aussehen. Auch langweilig. • (x :: rl, b) Dieses Pattern passt auf Tupel, deren erstes Element eine Liste ist (über das zweite wird nichts gesagt). Dies ist an dem verwendeten Konstruktor :: zu erkennen. Genauer gesprochen passt es auf solche Tupel, deren erstes Element eine Liste mit mindestens einem Element ist - der Konstruktor sagt: Da ist ein Element x, das von mir hätte auf die Liste rl gelegt werden können! Dazu muss natürlich beides da sein - ein x und eine Restliste, die auch die leere Liste sein kann. • x :: y :: z :: [] Nach dem Beispiel eben sollte das klar sein: Hier passen alle Listen, die genau drei Elemente haben. • Das ist ein Platzhalter für alles. lässt alles rein. Die Anwendung in ML Das ist ja alles schön und gut, nur müsst ihr es auch anwenden können. Dazu gibt es im Wesentlichen zwei Varianten, die ich an einem einfachen Algorithmus, der rekursiv die Elemente einer Integerliste aufaddiert, demonstrieren möchte: fun sum ( l : int list ) : int = case l of e::rl => e + sum(rl) | [] => 0; fun sum ( e::rl : int list ) : int = e + sum(rl) | sum ( [] ) = 0; Beide Funktionen leisten genau dasselbe. Die erstere Variante ist mehr dazu gedachte, in einer Funktion errechnete Werte zu matchen, die untere für das direkte Matchen von Funktionsparametern. Hierbei wird immer der der mit | (oder) getrennten Fälle ausgeführt, auf den der untersuchte Wert passt. Ihr seht, dass die im Pattern verwendeten Bezeichner an die matchenden Werte gebunden werden und somit in der Funktion verwendet werden können. Ausblick Es gibt viele Dinge, die man mit Pattern Matching anstellen kann, und ich möchte eure Entdeckerlust und Phantasie nicht unnötig einschränken. Probiert einfach herum, stöbert im Web - anders haben wir das auch nicht gelernt. Ich möchte trotzdem euren Blick auf einige Details lenken, die interessant sein könnten: • Was passiert, wenn ein Ausdruck auf mehrere der angegebenen Pattern matchen kann? • Was passiert, wenn mir Fälle fehlen? • Funktioniert das auch mit selbst geschriebenen Datentypen? Raphael Reitzig, 10. November 2007