Praxis der Funktionalen Programmierung (20. 10.)


Vorwort

Die Themen, die wir behandeln, gehören grob zu zwei Kategorien: Natürlich wandert das meiste im Laufe der Zeit von der zweiten in die erste Abteilung. Es wäre jedoch sehr umständlich, nur mit dem bereits exakt Definierten zu programmieren. Da würde es bis Weihnachten dauern, bis wir überhaupt auch nur ein Bild malen könnten. Andererseits entspricht dieses Vorgehen auch dem tatsächlichen Programmieren: man benutzt Bibliotheksfunktionen, ohne sich um deren Implementierung zu kümmern.

Einordnung

Wir sind im Abschnitt 1, einfache Programme, mit Grafik. Wir wollen an dessen Ende einige Parkettierungs-Aufgaben lösen können. Währendher lernen wir die grundsätzliche Struktur von Haskell-Programmen und -Datentypen kennen.

Definitionen

Die Syntax von Haskell-Programmen

Module
bestehen aus einer Folge von Typdeklarationen und Gleichungen (Definitionen)
Typdeklaration
hat die Form "Bezeichner :: Typ".
Definitionen
haben die Form "linke Seite = rechte Seite"
linke Seite
besteht aus einem Namen, eventuell gefolgt von Mustern
Muster
testet Übereinstimmung von Konstruktoren und/oder bindet lokale Bezeichner, ist entweder ein Bezeichner oder ein Konstruktor, eventuell gefolgt von Mustern
rechte Seite
ist ein Ausdruck
Ausdrücke

Einfache Typen

Wahrheitswerte: Boolean
Konstruktoren: False, True
Zeichen: Char
Konstruktoren: 'a', 'b', '\n', ...
Ganze Zahlen: Integer (beliebig lang!)
Konstruktoren: 12343511561356, 0xF0, ...
Zeichenketten: String
Ist gar kein einfacher Typ, sondern String = [ Char ], siehe Listen. Konstruktoren: Listenkonstruktoren und "foo", "bar\n", ...

Listen

Für einen Typ a bezeichnet [a] den Typ aller Listen mit Elementen vom Typ a. Eine Liste ist entweder leer, oder sie ist eine Zelle, die aus einem Kopf mit Typ a und einem Schwanz mit Typ [a] besteht.

Der Datentyp hat also zwei Konstruktoren, [] und (:). Diese verwenden wir auch in den Mustern. Beispiel:

länge :: [a] -> Integer
länge [] = 0
länge (x : xs) = 1 + länge xs
(Später programmiert man sowas mit einem fold.)

Statt (1 : (2 : (3 : (4 : [])))) schreiben wir kürzer [1, 2, 3, 4].

Tatsächlich können wir dafür sogar [1 .. 4] schreiben. Im Allgemeinen können wir auch eine Schrittweite angeben:

Prelude> [1, 3 .. 20]
[1,3,5,7,9,11,13,15,17,19]

Anwendung von Listen

Wir verwenden Listen in dem Programm, das Parkettierungen bestimmt (Quelltext hier). Die Idee ist, ein polygonal begrenztes Gebiet durch die Liste seiner Innenwinkel zu beschreiben. (Die Streckenlängen erwähnen wir nicht, die sind nämlich alle gleich groß). Zu implementieren ist dann das Aneinanderlegen zweier solcher Gebiete (d. h. Testen, ob es überhaupt paßt, und Bestimmen der neuen Randkurve).

List Comprehensions

Für das Durchlaufen und Erzeugen von Listen gibt es eine spezielle Syntax, die sich an der mathematischen Notation für Mengen orientiert. Wir erkennen sie an dem senkrechten Strich innerhalb von Listenklammern. Er trennt den Kopf (einen Ausdruck) vom Rumpf (einer Folge von Generatoren, Filtern und Bindungen)
Generator
ist ein Ausdruck nach einem Rückwärtspfeil. Er bindet die lokale Variable vor dem Pfeil. Diese ist ab dort im Rumpf sichtbar, und auch im Kopf der Comprehension. Beispiele:
Prelude> [ x * x | x <- [ 1, 2, 3, 4 ] ]
[1,4,9,16]
Prelude> [ x * y | x <- [1,2,3], y <- [2,3,5] ]
[2,3,5,4,6,10,6,9,15]
Wir können uns das als geschachtelte Schleifen vorstellen.
Filter
ist ein Ausdruck vom Wert Boolean. Beispiel
Prelude> [ x * x | x <- [0,1,2,3,4,5,6,7], 0 < mod x 3 ]
[1,4,16,25,49]
Bindung
hat die Form let Bezeichner = Ausdruck. Der lokale Bezeichner wird gebunden und ist ab dort und im Kopf sichtbar. Beispiel:
Prelude> [ y | x <- [0,1,2,3,4,5,6,7], let y = x * x * x ]
[0,1,8,27,64,125,216,343]

Beispiele

Aufgaben


Intuition

Der Interpreter Hugs

Hugs kann einen Modul-Quelltext laden und dann Ausdrücke auswerten oder Aktionen ausführen, die in diesem Modul definiert sind.

Hugs unterscheidet dabei: Ist der Ausdruck vom Typ IO (), das heißt, eine Aktion, dann wird diese ausgeführt. Beispiel:

Prelude> putStrLn "hello"
hello
Bei Ausdrücken anderer Typen zeigt Hugs eine ASCII-Repräsentation des Wertes an. Beispiel:
Prelude> take 10 [1..]
[1,2,3,4,5,6,7,8,9,10]
Beachte: nicht für alle Typen existiert so eine Repräsentation. Das führt zu solchen Fehlermeldungen:
Prelude> show id
ERROR: Illegal Haskell 98 class constraint in inferred type
*** Expression : show id
*** Type       : Show (a -> a) => [Char]
Für nutzerdefinierte Typen muß man die Repräsentation entweder selbst festlegen oder vom Compiler explizit generieren lassen. Siehe später bei Typklassen.

Elementare Grafik

(Das Graphics-Paket muß installiert sein.)

Ein Grafik-Programm ist ebenfalls eine Aktion. Es sieht im einfachsten Fall so aus:

import SOEGraphics
main = runGraphics $ do
     let s = 500
     w <- openWindow "Grafik" (s, s)
     drawInWindow w $ polyline [(0,0), (s,s)]
     k <- getKey w
     closeWindow w
Beachte: nach dem "do" müssen die Zeilenanfänge genau untereinander stehen.

Was das "do" überhaupt ist, behandeln wir später ausführlich.

Kasten-Grafik

Mit der elementaren Grafik müssen wir ständig in globalen Koordinaten rechnen. Ich habe eine kleine Bibliothek geschrieben (Quelltexte hier), die Zusammensetzungen aus Kästen gestattet, innerhalb derer wir mit lokalen (oder gar keinen) Koordinaten arbeiten. Die Implementierung sehen wir uns später an. Die Benutzung geht so:
import Bild
...
bild $ neben ( über (kreis 1) (kreis 2) ) ( quadrat 3 ) 
Einige Datentypen "t" implementieren die Methode "form :: t -> Bild", und es gibt die Abkürzung "zeige x = bild (form x)". Beispiel
import Bild
import Ding
import Form
...
zeige [ [ rot, blau, gelb ], [schwarz, blau] ]
Zu Methoden (und Typklassen und Instanzen) hören wir später mehr.

best viewed with any browser


http://www.informatik.uni-leipzig.de/~joe/ mailto:joe@informatik.uni-leipzig.de