2.
Definition von Funktionen fact und comb
|
|
Die Funktion fact |
Neben dem 'Haskell-Fenster' sollte man immer noch zusätzlich ein
Editorfenster geöffnet haben. Hier können wir den Quelltext eigener,
nicht von Haskell bereits zu Verfügung gestellter Funktion implementieren.
In unserem ersten Beispiel schreiben wir den Quelltext für eine Funktion
die die Fakultät der übergebenen Zahl berechnet. Wir nenn diese Funktion fact.
Was schreiben wir in die ASCII-Datei?
module EigeneFunktionen where In der ersten Zeile wird der Name festgelegt, unter dem man die Funktionen sammelt. In unserer Datei ist eine Funktion fact definiert. Dies geschieht in zwei Teilen.
|
Danach
speichern wir die Datei als
Kapitel02.hs in einem Verzeichnis unserer Wahl ab.
Damit kennt das Haskell-System die neue Funktion noch nicht. Das geht so: Wir wechseln wieder in unser GHCi-Fenster. Mit Prelude> :cd <Pfadname> wechselt man das aktive Verzeichnis. Danach lädt man die Datei mit Prelude> :load Kapitel02.hs Das System meldet, dass der Kode in der Datei kompiliert wird und die Module von EigeneFunktionen geladen sind und damit zum Aufruf bereit stehen. Erkennt der Compiler Fehler, so werden diese hier gemeldet. Wir können nun die eben geschriebene Funktion fact benutzen. *EigeneFunktionen> fact 8 Die Datei Kapitel02.hs lässt sich um zusätzliche Funktionen erweitern, z.B. durch eine Funktion, die Binomialkoeffizient unter Verwendung der Funktion fact berechnet: Wir fügen der Datei die folgenden
Zeilen hinzu: |
|
comb
|
comb ::
Integer ->
Integer ->
Integer comb
definiert den Binomialkoeffizienten "a über b" und ist, wie wir in
der Mathematik sagen, eine Funktion mit zwei Funktionsvariablen und einem
Funktionswert, . Hier sagen wir gerne, dass
comb
zwei Parameter und einen
Rückgabewert hat. In der
ersten Programmzeile unserer neuen Funktion erkennen wir der Reihe nach
die Typen der beiden Parameter und des Rückgabewertes. Die zweite Zeile erklärt sich selbst, denn es gilt.
wobei der Bruchstrich eine Ganzzahldivision (`div`, die div-einrahmenden Striche sind Accent-grave-Zeichen) repräsentiert. `div` ist also eine infix-notierte Funktion Wir berechnen die Chance einen 6er im Lotto zu gewinnen, setzen zuvor die Ausgabe zurück, dass kein Ausgabetyp mehr angezeigt wird: *EigeneFunktionen> :unset +t
|
toUpperCase |
toUpperCase
soll eine Funktion sein, die einen Klein-Buchstaben in einen
Groß-Buchstaben umwandelt und einen Groß-Buchstaben unverändert lässt.
Genauer müssten wir sagen, toUpperCase ist eine Funktion die einem
Klein-Buchstaben seinen entsprechenden Groß-Buchstaben zuordnet.
Groß-Buchstaben werden auf sich selbst abgebildet. Bei der Implementierung
helfen uns zwei Funktionen ord
und chr.
Dabei gilt ord :: Char -> Integer
und bildet ein Zeichen auf seinen ASCII-Code ab. Und umgekehrt
chr :: Integer -> Char
bildet eine Zahl, als ASCII-Code interpretiert, auf das entsprechende
Zeichen ab. die beiden Funktionen ord und chr stehen anders als bei
Winhugs beim GHCi im Prelude nicht zur Verfügung und muss deshalb mit
import Char
importiert werden. import Char
//muss an den Anfang der Datei
Test der Funktion
*EigeneFunktionen> toUpperCase 'z' |
|
Neben der Tatsache, dass man
'Deklarationen' zusammengefasst an den Anfang stellen kann, lernen wir
hier ein Verfahren kennen, das nicht nur bei Haskell sondern allgemein in
der Informatik von großer Wichtigkeit ist, dem pattern matching, oder ins
Deutsche übersetzt dem Musterabgleich. Betrachten wir zunächst die Kodes
hinter den senkrechten Strichen | (guards genannt). Liefert die Funktion
isLow
bei einem Parameterwert von z.B. 'a' den Wert True, so ist der
Funktionswert chr (ord c - (ord
'a' - ord 'A')). Nachrechnen
zeigt, dass das das Zeichen 'A' ist. Dem senkrechten Strich folgt
also eine Bedingung, die als Funktions formuliert ist. Betrachten wir den
zweiten senkrechten Strich: Die Funktion
otherwise
liefert generell den Wert True.
Was passiert also nun? Die Folge der Ausdrücke hinter den senkrechten
Strichen wird von oben nach unten ausgewertet. Liefert der Ausdruck hinter
dem ersten Strich den Wert True,
wir haben damit die Wertezuweisung
c
-> chr (ord c - (ord 'a' - ord
'A')), wird der Rest wird nicht
mehr abgearbeitet. Kann der Ausdruck hinter dem ersten senkrechten Strich
aber nicht mit True
abgeglichen werden, so wird ein Abgleich mit
True
in der nächsten Zeile versucht. Die Zuweisung c -> c erfolgt also genau
dann wenn isLow
für c False
liefert, d.h. c ist ein Großbuchstabe und damit liefert c -> c genau das
gewünschte. Der Kode der Funktion isLow lässt sich leicht verstehen, wenn
man weiß, dass <, <=, > und >= Funktionen (infix) sind, die hier zwei
Zeichen auf lexikalische Ordnung prüfen und dass mit && eine logische
AND-Funktion (ebenfalls infix) implementiert ist. |
Bemerkung |
Die Funktionen
toUpperCase
und toLowerCase
sind in dem Modul Prelude
bereits implementiert, heißen dort
toUpper
und toLower.
Entsprechend heißt die Hilfsfunktionen
isLow
dort isLower.
So wird ein Überschreiben der Funktionen verhindert. |
Implementierung einer infix-notierten Funktion |
Wir haben im letzten Abschnitt die
'.'-Funktion zur Komposition von Funktionen kennen gelernt. Wir wollen
jetzt eine solche Funktion selbst implementieren. Wir wollen sie
'#'-Funktion nennen. (#) ::
(Integer -> Integer) -> (Integer -> Integer) -> (Integer -> Integer) Die runden Klammern um den Funktionsnamen legen fest, dass die hier deklarierte Funktion ein infix-Operator ist. Die Variablen der Funktion f, g und f#g sind Funktionen, ihre Typen also jeweils (Integer -> Integer). Testen der Koposition: *EigeneFunktionen> (succ#succ) 3 |
Damit ist aber die #-Funktion noch
lange nicht der '.'- Koposition gleichwertig. Das nächste Beispiel zeigt
dies: *EigeneFunktionen> (reverse#reverse)
"Zappa" |
|
Downlaod: Kapitel02.hs |
Wir ändern die Definition der
'#'-Funktion ab und ersetzen Integer durch Typenvariable und machen unsere
Funktion '#' polymorph: (#) :: (b -> c) -> (a -> b) -> (a -> c) (f # g) x = f (g x) Tests: *EigeneFunktionen> (succ#succ) 4 Verwenden wir Typenvariable
erkennen wir jetzt auch sehr schön, dass f nach g ausgewertet wird. |
Übung 1 | Schreiben Sie eine Datei Binomi.hs in der die Funktionen binomi1, binomi2 und binomi3 definiert werden. Die Spezifikation von binomi1 ergibt sich aus der Forderung: binomi1 berechnet für die übergebenen Werte a und b (a+b)2, wobei a und b vom Typ Integer sein sollen. Die Spezifikationen von binomi2 und binomi3 sind entsprechend. |
Übung 2 | Schreiben Sie eine Datei Flaechen.hs mit den Funktionen quadratFlaeche, rechteckFlaeche und kreisFlaeche, benutzen sie als Variablentyp die einfache Gleitkommazahl Float. Die Zahl pi ist schon vordefiniert und kann benutzt werden. |
Übung 3 | Implementieren Sie toLowerCase. Die Funktionalität ergibt sich aus der Analogie zu toUpperCase. |
Übung 4 | Implementieren Sie eine Funktion inc, die die Funktion succ nachempfindet und ein Funktion dec, die eine Zahl auf ihr um 1 dekrementiere Zahl abbildet. So sollten (dec.inc) und (inc.dec) jeweils die identische Abbildung sein. |
Übung 5 | Schreiben Sie eine Funktion istPalindrom, die einen eingegebenen String prüft, ob er ein Palindrom ist. |
Übung 6 | Programmieren Sie den Infix-Operator '+++' der die gleiche Funktionalität hat wie der normale Infix-Operator '+' für Integer-Werte. |
Lösungen | Zu den Lösungen |
(C) Michael pohlig 2005 www.pohlig.de |