\startproduct Tag6 \environment HaskellCourseEnv \startnotmode [complete] \setupheadnumber [Tag] [5] \stopnotmode \Tag [Tag6] {Module, benannte Funktionen und Rekursion} Wir verlassen nun die abgeschlossene kleine Welt des interaktiven Interpreters und lernen, wie man richtige Haskell-Programme schreibt. Im Interpreter haben wir bislang haupts"achlich Ausdr"ucke ausgewertet. Ein Haskell-Programm aber erlaubt wesentlich mehr als mit Ausdr"ucken allein m"oglich ist: Eigene Funktionen etwa (nicht nur mit Lambda-Abstraktion, sondern mit eigenem Namen) sind zwar im GHCi mittels der \type{let}-Konstruktion m"oglich, aber einfacher in einer Datei zu editieren. Zudem bietet ein Programm auch die M"oglichkeit, eigene Datentypen und Pr"adikate (so wie \type{Num} und \type{Eq}) zu definieren, und Funktionen in wiederverwertbare Einheiten, sogenannte Module zu organisieren. Ein Programm besteht aus mehreren Modulen. Jedes Modul steht in einer eigenen Datei. Die bereits vielzitierte \type{Prelude} ist selbst ein Modul, welches implizit in jedes andere Modul importiert wird und dessen Funktionen daher "uberall zur Verf"ugung stehen. Haskell-Module k"onnen mit jedem beliebigen Editor erstellt werden, der Dateien im Textformat abspeichern kann. Wenn m"oglich, sollte in der Konfiguration die Verwendung von Tabulatoren ausgeschaltet oder die Tabulatorweite auf 8 Zeichen eingestellt werden. Haskell bietet n"amlich im Vergleich zu vielen anderen Sprachen die M"oglichkeit, das Layout des Programms dazu zu benutzen, Anweisungen zu gruppieren und dadurch unn"otigen Ballast an Klammern und Trennsymbolen einzusparen. Das wird erst sp"ater wirklich relevant, aber eine Einstellung im Editor schon jetzt hilft, Mi"sverst"andnissen zuvorzukommen. Der Dateiname sollte genau einen Punkt enthalten. Der Teil vor dem Punkt sollte dem Modulnamen entsprechen (und in Betriebssystemen mit Gro"s-/Kleinschreibungsunterscheidung mit einem Gro"sbuchstaben beginnen, weil Haskell-Modulnamen mit einem Gro"sbuchstaben beginnen m"ussen). Als Endung w"ahlen wir \type{lhs}. Das steht f"ur \quotation{literate Haskell}. Jede Zeile in der Datei, die {\em nicht} mit \type{> } (also einem \quotation{Gr"o"ser}-Zeichen gefolgt von einem Leerzeichen) beginnt, gilt als Kommentar. Zwischen Kommentar und Code-Zeilen mu"s immer noch mindestens eine Leerzeile liegen. Hier im Text werde ich ebenfalls Haskell-Zeilen, die als Code f"ur ein Module bestimmt sind, mit \type{> } einleiten, zur besseren Unterscheidung von Interaktionen im Haskell-Interpreter, die weiterhin mit dem vollen Interpreter-Prompt beginnen. Ein Modul beginnt normalerweise mit einer Deklaration seines Namens, also etwa: \startCode > module Tag6 where \stopCode Die Worte \type{module} und \type{where} sind sogenante \quotation{Schl"usselworte} und m"ussen genau in dieser Form dort stehen. Der Modulname ist hier \type{Tag6}. Er mu"s, wie bereits erw"ahnt, mit einem Gro"sbuchstaben beginnen. Als vorl"aufig einzigen Inhalt von Modulen lernen wir Funktionsdefinitionen (mit zugeh"origen Typsignaturen) kennen. Benannte Funktionen (und auch konstante Werte) k"onnen einfach mit Hilfe des Gleichheitszeichens eingef"uhrt werden: \startCode > identitaet = (\x -> x) \stopCode Wir m"ussen nat"urlich aufpassen, da"s wir keine Namen verwenden, die schon von der Prelude in Beschlag genommen sind, etwa in diesem Fall der Name \type{id} f"ur dieselbe Funktion. Wenn wir das Modul jetzt unter \type{Tag6.lhs} abspeichern, dann k"onnen wir den \GHCi\ oder den \Hugs\ anschlie"send mit dem Dateinamen als Parameter aufrufen, und erhalten entweder eine Fehlermeldung "uber den Inhalt des Moduls, oder aber, anders als bisher, den Prompt \type{Tag6> }, der anzeigt, da"s das Modul geladen ist und zur Verf"ugung steht. Alternativ kann der Interpreter mit der Anweisung \type{:l} auch \quotation{im Betrieb} angewiesen werden, das Modul zu laden: \startHaskell Prelude> :l Tag6 Tag6> identitaet 2 2 it :: Integer Tag6> :t identitaet forall t. t -> t \stopHaskell Tats"achlich k"onnen wir also den von uns eingef"uhrten Namen \type{identitaet} nun verwenden, wie das Beispiel bereits demonstriert. \startAufgabe Schreibe ein Modul mit Namen \type{Tag6Test}, speichere es in einer Datei \type{Tag6Test.lhs} ab und f"uge eine passende Modulkopfzeile sowie die Definition von \type{identitaet} ein. Lade dann das Modul auf die zwei beschriebenen Weisen in den Haskell-Interpreter. \stopAufgabe Haskell bietet die M"oglichkeit, selber definierten Funktionen eine Typsignatur zu verpassen. Dies ist, wie wir bereits gesehen haben, nicht n"otig (es funktioniert ja auch so), aber empfehlenswert und guter Stil. Zum einen dient die Typsignatur bereits der Dokumentation der Funktion. Schon am Typ kann man einiges "uber das Verhalten der Funktion ablesen. Zum anderen sind die Typfehlermeldungen im allgemeinen pr"aziser, wenn man Typsignaturen verwendet. Dann kann der Compiler n"amlich konkret auf Diskrepanzen zwischen der Intention (der Typsignatur) und der tats"achlichen Realit"at (der Definition der Funktion) hinweisen, w"ahrend er andernfalls erraten mu"s, welchen Typ die Funktion wohl haben sollte. Im Falle der Identit"atsfunktion k"onnen wir schreiben: \startCode > identitaet :: a -> a \stopCode Das \type{::} liest man als \quotation{hat den Typ}. Die Quantifizierung mittels \type{forall} wird weggelassen! Alle vorkommenden Variablen in einer Typsignatur gelten implizit als durch ein \type{forall} quantifiziert. Es ist sehr wahrscheinlich, da"s in zuk"unftigen Sprachversionen von Haskell das \type{forall} explizit hingeschrieben wird oder es zumindest erlaubt ist, es explizit zu schreiben. Schon jetzt unterst"utzen sowohl \Hugs\ als auch \GHC\ diese Variante, wenn man einen \quotation{erweiterten Modus} des Compilers aktiviert. Normalerweise schreibt man die Typsignatur einer Funktion direkt vor deren Definition. Die Funktion zum Vertauschen von Paaren aus den Aufgaben der letzten Lektion etwa k"onnen wir in unserem Modul so definieren: \startCode > swap :: (a,b) -> (b ,a ) > swap = \pair -> (snd pair,fst pair) \stopCode \startHaskell Tag6> swap (1,'c') ('c',1) it :: (Char, Integer) \stopHaskell Lambda-Abstraktion kann bei der Einf"uhrung eines Funktionsnamens auf angenehme Art und Weise abgek"urzt werden. Wir k"onnen \type{swap} alternativ definieren als \startCode > swap' :: (a,b) -> (b ,a ) > swap' pair = (snd pair,fst pair) \stopCode Auch mehrere Argumente (statt wiederholter Lambda-Abstraktion) k"onnen auf diese Art und Weise eingef"uhrt werden, hier zum Beispiel eine Funktion, die drei Integer-Werte auf Gleichheit "uberpr"uft: \startCode > triEqual :: Integer -> Integer -> Integer -> Bool > triEqual a b c = a == b && b == c \stopCode Hier sehen wir auch gleich eine weitere Verwendung von Typsignaturen. Der deklarierte Typ kann {\em spezieller} sein als eigentlich n"otig. {\em Ohne} Typsignatur w"urde der \GHCi\ f"ur \type{triEqual} den Typ \type{forall a. (Eq a) => a -> a -> a -> Bool} ableiten. (Normalerweise will man auch den allgemeinsten Typ haben, aber manchmal kann man auch durch solche Spezialisierungen die Verst"andlichkeit von Fehlermeldungen verbessern.) Der wahre Vorteil, der durch die Einf"uhrung von Namen f"ur unsere Funktionen entsteht, ist die M"oglichkeit zur Verwendung von Rekursion. Rekursion ist in der funktionalen Programmierung allgegenw"artig und hat etwa den Stellenwert von Schleifen in imperativen Programmiersprachen. Als Beispiel schreiben wir die Funktion \type{enum}, die zwei Integer als Argumente nimmt, wobei der zweite gr"o"ser sein sollte als der erste. Als Ergebnis wollen wir die Liste aller Zahlen, die zwischen der ersten und der letzten (einschlie"slich der Grenzen) liegen. \startCode > enum :: Integer -> Integer -> [Integer] > enum lower upper = if lower == upper > then [lower] > else lower : enum (lower+1) upper \stopCode Zun"achst "uberpr"ufen wir, ob die beiden Grenzen des Bereichs gleich sind. In diesem Fall k"onnen wir die richtige Antwort sofort geben: Die einelementige Liste, die diesen Begrenzungswert enth"alt. Andernfalls ist die \quotation{untere} Schranke auf jeden Fall in der Ergebnisliste, und zwar als Kopfelement (wir verwenden daher den Cons-Operator \type{:}). Die Restliste ist genau der Bereich von \type{lower+1} bis \type{upper}, also k"onnen wir \type{enum} rekursiv daf"ur verwenden. \startHaskell Tag6> enum 2 5 [2,3,4,5] \stopHaskell Eigentlich ist \type{lower} immer in der Ergebnisliste, unabh"angig vom Ausgang des Vergleiches. Wir k"onnen deshalb alternativ schreiben: \startCode > enum' :: Integer -> Integer -> [Integer] > enum' lower upper = lower : if lower == upper > then [] > else enum (lower+1) upper \stopCode Diese Variante ist ein gutes Beispiel daf"ur, da"s \type{if} in Haskell ein Teil von Ausdr"ucken und kein "ubergeordneter Befehl ist: Das gesamte \type{if}-Konstrukt kann hier problemlos als zweites Argument von ":" fungieren. Nat"urlich funktionieren beide Funktionen nicht ordentlich, wenn die zweite Zahl kleiner ist als die erste. Die Eingabe \startHaskell Tag6> enum 5 2 \stopHaskell f"uhrt zu einer Endlosschleife, die man im Interpreter mit \keyboard{Control-C} abbrechen kann. \startAufgabe Schreibe \type{enum} so um (etwa als \type{enumX}), da"s die beiden Grenzen nicht mit in der Ausgabeliste stehen. Auch hier mu"s die Funktion nicht funktionieren, wenn der zweite Wert nicht mindestens so gro"s ist wie der erste. \startHaskell EnumX> enumX 1 1 [] EnumX> enumX 2 3 [] EnumX> enumX 3 6 [4,5] \stopHaskell \stopAufgabe \startAufgabe Schreibe eine Variante von \type{enumX}, die f"ur den Fall, da"s das zweite Argument kleiner ist als das erste, die Zahlen zwischen beiden Werten in umgekehrter Reihenfolge zur"uckliefert. \startHaskell EnumX> enumX' 3 7 [4,5,6] EnumX> enumX' 7 3 [6,5,4] \stopHaskell \stopAufgabe Die Prelude stellt die Vergleichsoperatoren \type{<}, \type{<=}, \type{>=} und \type{>} zur Verf"ugung. Ein kleiner Tip zum einfacheren Entwickeln mit der interaktiven Umgebung: Wenn man ein Modul, welches von \GHCi\ oder \Hugs\ bearbeitet hat, gleichzeitig ver"andert, so kann man durch Eingabe von \type{:r} an der Eingabeaufforderung ein erneutes Laden des (ver"anderten) Moduls erreichen. Man mu"s also weder den Interpreter neu starten noch jedesmal das Modul explizit mit \type{:l} laden. \startAufgabe Schreibe die Fakult"atsfunktion \type{factorial :: Integer -> Integer} so, da"s sie auf nichtnegativen Zahlen ein korrektes Ergebnis liefert: \startHaskell Factorial> factorial 0 1 it :: Integer Factorial> factorial 42 1405006117752879898543142606244511569936384000000000 it :: Integer \stopHaskell \stopAufgabe \startLernziele \item Haskell-Programme sind in Modulen organisiert. \item Module werden in Textdateien gespeichert und k"onnen von den Interpretern geladen werden. \item Module k"onnen Funktionsdefinitionen und Typsignaturen enthalten. Den Funktionen k"onnen nun Namen gegeben werden. \item Rekursion ist eines der wichtigsten Programmierprinzipien in der funktionalen Programmierung. \stopLernziele \stoptext \stopproduct