Eine kleine Einführung in die rein funktionale Programmierung - Teil 2
In einem vorigen Beitrag haben wir das Vorhaben begonnen, eine Schneckenwelt zu implementieren. Zur Erinnerung:
[…] nehmen wir uns ein konkretes Beispiel vor: Es geht um die Simulation und Visualisierung einer Welt (vielleicht für ein Videospiel), in der sich Schnecken bewegen […]. Die Schneckenwelt ist zweidimensional, und wir fangen mit sehr sturen Schnecken an, die sich stets in die gleiche Richtung bewegen und sich nicht davon abhalten lassen.
In diesem Posting kümmern wir uns erst einmal um die individuellen Schnecken, die wir in einem späteren Posting dann in der Schneckenwelt anordnen. In einem dritten Posting werden wir das Programm so erweitern, daß die Schnecken Schleimspuren hinterlassen und den Schleimspuren anderer Schnecken („die stinken“) ausweichen. Das ganze visualisieren wir dann dergestallt, daß es so aussieht:
Wir erweitern den Code vom letzten Mal und ordnen Schnecken in einer Schneckenwelt an:
Das snails
-Feld der Schneckenwelt besetzen wir mit einer Liste
aller Schnecken. Die eingebaute list
-Funktion macht aus den
Schnecken s1
, s2
und s3
eine Liste - sw1
ist dann die
Schneckenwelt daraus.
Was können wir mit so einer Schneckenwelt anfangen? Zwei Sachen wären nett:
- die Schneckenwelt grafisch anzeigen
- die Schneckenwelt animieren, so daß die Schnecken sich bewegen
Im letzten Beitrag hatten wir bereits eine einzelne Schnecke
in einer Szene plaziert. Um die ganze Schnecke anzuzeigen,
müssen wir mit einer leeren Szene
anfangen und sukzessive mit draw-snail
eine Schnecke nach der
nächsten in die Szene plazieren. Bei jeder Schnecke kommt dabei
wieder eine neue Szene heraus, bis schließlich alle Schnecken in der
Szene untergekommen sind. Hier ist die Funktion, die das macht:
Diese Funktion benutzt die eingebaute foldl
-Funktion, eine der
Eckpfeiler der funktionalen
Programmierung.
Die
foldl
-Funktion
iteriert über einer Liste und transformiert bei jedem Schritt einen
Zustand unter Zuhilfenahme des jeweils nächsten Listenelement in einen
neuen Zustand.
In diesem Fall beginnt foldl
mit der leeren Szene, die von
(empty-scene width height)
erzeugt wird, schnappt sich das erste
Listenelement der Schneckenliste (snail-world-snails sw)
und wendet
auf beides draw-snail
an. Dabei kommt wiederum eine Szene heraus,
in der jetzt die erste Schnecke plaziert ist. Die foldl
-Funktion
schnappt sich jetzt die nächste Schnecke, und ruft wieder draw-snail
auf, und wieder kommt eine Szene heraus, und so weiter bis die Liste
zu Ende ist. Am Ende kommt dann eine Szene heraus, in der alle
Schnecken plaziert sind.
Der obige Code benötigt noch Definitionen für die Breite width
und
die Höhe height
der Szene:
Damit ist die grafische Darstellung fertig; es fehlt noch die
Animation. Dazu bewegen wir einfach alle Schnecken einer
Schneckenwelt mit Hilfe der schon geschriebenen Funktion move-snail
:
Diese Funktion füttert die Schneckenliste der Schneckenwelt in die
eingebaute Funktion
map
.
Diese wendet eine gegebene Funktion auf jedes Element einer Liste an
und macht aus den Ergebnissen wieder eine Liste. In diesem Fall
wendet sie die Funktion move-snail
auf jede Schnecke der
Schneckenwelt an und macht aus den resultierenden bewegten Schnecken
wieder eine Liste; next-snail-world
macht daraus dann eine neue
Schneckenwelt.
Zu guter letzt können wir das ganze noch animieren. Dazu benutzen
wir eine weitere Library, die bei Racket dabei ist, nämlich
2htdp/universe
.
Um sie einzubinden, müssen wir am Anfang noch eine require
-Form
einfügen:
Für unsere Animation benutzen wir die
big-bang
-Form:
Das Argument sw1
ist die Anfangs-Schneckenwelt. Die Animation läßt
nun eine getaktete Uhr laufen, und bei jedem Taktschlag (alle 0.2
Sekunden) wendet sie die Funktion next-snail-world
auf die
Schneckenwelt an - das besagt die on-tick
-Klausel. Nach jedem
Taktschlag wird die aktuelle Schneckenwelt angezeigt durch die
Funktion draw-snail-world
, was die to-draw
-Klausel besagt.
Zum Schluß lohnt es sich, die rein funktionale Programmierung noch
einmal in Perspektive zu setzen: Die Funktion next-snail-world
bewegt alle Schnecken gleichzeitig. Angenommen,
move-snail
würde imperativ funktionieren, also die Schnecke in situ
modifizieren. Beim aktuellen next-snail-world
würde das keinen
Unterschied machen, da die Schnecken alle unabhängig voneinander
wären. Was aber, wenn die Schnecken aufeinander achten müßten, also
z.B. anderen Schnecken oder deren Schleimspuren ausweichen müßten?
Dazu mehr in einem zukünftigen Beitrag.
Den Code zu diesem Beitrag können Sie übrigens hier herunterladen.
Weiter geht es in Teil 3.