Beispiel des Modulkonzeptes in Haskell | Beispiel des Modulkonzeptes in Pascal |
Definition
eines Moduls : -- alle Funktionen und Datentypen des Moduls exportieren module Tree where ... -- nur einige Funktionen und Datentypen exportieren -- Mit dem Datentyp Tree werden noch zusätzlich seine -- Daten-Konstruktoren Leaf und Branch exportiert module Tree (Tree (Leaf,Branch),fringe) where data Tree a = Leaf a | Branch Tree a Tree a fringe = ... Verwendung des Moduls durch andere Programmteile: -- nur einige Funktionen und Datentypen importieren module Main (main) where import Tree (Tree(Leaf,Branch),fringe) main = ... -- alle Funktionen und Datentypen importieren module Main (main) where import Tree main = ... |
Definition
eines Moduls (Unit):
UNIT Unitname; TYPE TestTyp= ... IMPLEMENTATION PROGRAM Testprogramm; |
Die erste Form des "Verklebens" - Das "Verkleben" von Funktionen
Es sei folgende allgemeine
Listendefinition
gegeben:
listof X ::= nil | cons X (listof)
X
Die Summe aller Elemente der Liste
sei folgendermaßen definiert:
sum nil = 0
sum (cons num list) = num + sum list
Aus der rekursiven Definition der Summenbildung lässt sich ableiten das es sich hierbei zum einen um einen generellen rekursiv arbeiten Algorithmus der alle Elemente einer Liste bearbeitet und eine Funktion zur Ermittlung einer Summe handelt. Aus dieser Erkenntnis heraus lässt sich das Problem weiter modularisieren.
Der rekursive Teil wird in diesem
Fall
konventionell als "reduce" bezeichnet
sum = reduce add 0
Wobei Add die Funktion zur
Summenbildung
darstellt:
add x y = x + y
Die Definition von reduce sieht
folgendermaßen
aus:
(reduce f x) nil = x
(reduce f x) (cons a l) = f a
((reduce
f x) l)
Auf Basis des rekursiven
Algorithmus
lassen sich nun auch ohne weitere Programmierung andere Operationen auf
Listen durchführen:
Ein Beispiel:
Wenn g
und f Programme sind,
dann
ist (g . f) ein Programm bei dem f
die Eingabedaten für g
generiert.
g (f input)
Hierbei spielt ein Mechanismus eine wichtige Rolle der als "strikte
Synchronisation" bezeichnet wird. Bei der "strikten Synchronisation"
muß
sich der Programmierer nicht darum kümmern wie die Daten die die
Funktion f
produziert zwischengespeichert werden müssen um von g
ausgewertet werden zu können. Funktion f
stellt stets nur soviele Daten zur Verfügung wie g
anfordert. Die beiden Programme laufen somit in "strikter
Synchronisation". f
kann zudem auch ein Programm sein das eine unendliche Menge an Daten
produziert.
Sobald g beendet wird,
wird
auch f beendet. Diese Art
der Auswertung sorgt also dafür das das Programm f
nur so lange läuft wie unbedingt erforderlich. Sie wird auch als
"Lazy
Evaluation" bezeichnet und stellt einen sehr wichtigen Baustein
für
die Modularisierung von Programmen innerhalb der funktionalen
Programmierung
dar.
Der folgende Algorithmus wird auch als alpha-beta
Algorithmus bezeichnet und dient der Abschätzung wie sich ein
Spiel
möglicherweise entwickeln könnte.
Aus dieser Information können dann optimale Spielzüge
ermittelt
werden. Zur Veranschaulichung wird hier das Spiel Tic
Tac Toe verwendet.
Die gedankliche Implementierung:
Die Spielpositionen werden als Object vom Typ Position
abgebildet.
Eine Funktion moves soll
die von einer Position aus möglichen weiteren Spielschritte
ermitteln,
die für einen einzelnen Spielzug möglich sind.
moves: position -> listof position
Bei dem hier als Beispiel verwendeten Spielprinzip ist es stets
aufgrund
des abwechselnden Setzen von Steinen stets möglich zu Ermitteln
welcher
Spieler als nächster an der Reihe ist, daher ist es nicht
erforderlich
diese Information in irgendeiner Form separat zu speichern.
Als nächstes soll eine Baumstruktur abgebildet werden, die alle
von einer Position ausgehenden möglichen Spielzüge speichern
kann.
Die Baumstruktur kann durch eine wiederholte Anwendung der
Function moves aufgebaut
werden. Begonnen wird hierbei mit der in der Wurzel des Baumes
gespeicherten
Position.
gametree p =
reptree
moves p
Desweiteren wird ein Funktion benötigt die in der Lage
ist zu Ermitteln ob eine bestimmte Folgeposition für den Spieler
der
an der Reihe ist vorteilhaft ist oder nicht.
Gehen wir davon aus das eine solche Funktion mit dem Namen static
gegeben sei und folgendermaßen definiert ist:
static:
position
-> number
Für unseren einfachen Fall könnte diese Funktion eine
1 für eine Position zurückliefern bei denen der Computer
bereits
gewonnen, eine -1 für das Verlieren und andernfalls eine 0.
Nachdem
ein Spielzugbaum generiert worden ist, kann dieser mit Hilfe der
Funktion maptree
staticin einen Baum von Zahlen umgewandelt werden, über den
sich dann der nächste vorteilhafte Spielzug ermitteln
lässt. Maptree sei
eine als gegeben angenommene Funktion die die einzelnen Elemente eines
Baumes durchläuft und auf sie eine übergebene Funktion wie
zum
Beispiel static anwendet.
Die grundsätzliche Tatsache das Spielbäume auch von
unendlicher
Länge sein können wird noch im Folgenden betrachtet. Bei
einem
Spiel wie Tic Tac Toe sind
die Spielzugbäume endlich, jedoch bei Spielen wie zum
Beispiel Schach sind
unendliche Spielpartien prinzipiell möglich.
Um nun einen passenden Spielzug auswählen zu können
werden noch Funktionen benötigt die Ermitteln welcher
nächster
Spielzug denn nun tatsächlich der Beste ist. Ausgehend von den
möglichen
Werten -1 (Computer verloren) , 0 , +1 (Computer gewonnen) wird
für
die Spielzugwahl des Computers eine Funktion benötigt die
die größte Zahl aus einer Liste von Spielzugbewertungen
heraussucht. Der Gegenspieler würde eine Funktion einsetzen die
das
Minimum ermittelt.
Es wird also der Spielverlauf gesucht der zu einem Sieg führt
hierbei werden alle möglichen Unterpositionen der aktuellen
Spielposition
untersucht.
Führt eine Position zu einem Sieg des Computers (maximise), dann
wird diese als nächster Spielzug ausgewählt.
Diese minimise
und maximise
Funktionen seien folgendermaßen definiert:
maximise (node n nil) = n
maximise (node
n sub) = max (map minimize sub)
minimize (node
n nil) = n
minimize (node
n sub) = min (map maximize sub)
Zum jetzigen Zeitpunkt wäre es bereits möglich eine Funktion zu definieren die einen passenden Spielzug auswählt. Sie könnte folgendes Aussehen haben:
evaluate
=
maximise . maptree static . gametree
Es gibt letzendlich noch ein Problem in bezug auf sehr
umfangreiche
oder sogar unendliche Baumstrukturen. Die vorstehende Definition
würde
dann entweder erst nach sehr langer Zeit oder gar nicht zu einem
Ergebnis
kommen. Daher kommt eine weitere Funktion zum Einsatz die es
ermöglicht
nach einer bestimmten Tiefe die Rekursion abzubrechen. Dies ist nur
aufgrund
des Lazy Evaluation
Mechanismus
so einfach möglich wie es in Folge beschrieben ist.
Die erforderliche Funktion sei prune(engl.
kürzen, abschneiden).
prune 0 (node a
x) = node a nil
prune n (node
a x) = node a (map (prune (n-1)) x)
Die verbesserte Auswertungsfunktion wird nun folgendemaßen
geschrieben:
evaluate =
maximise
. maptree static . prune 5 . gametree
Sie betrachtet maximal noch 5 weitere Spielzüge, bevor
sie ein Ergebnis liefert.
Das beschriebene Beispiel verdeutlicht die Leistungsfähigkeit
der Lazy
Evaluation und den Einsatz vonFunktionen
Höherer Ordnung als Hilfmittel der Modularisierung und
Optimierung von Programmen.
Das Paket besteht derzeit aus folgenden Komponenten:
Die folgende unvollständige Liste soll ein paar der verfügbaren Entwicklungshilfen auflisten:
Für Haskell:
Der Haskell Interpreter (HUGS)
Ein kleiner portabler in C geschriebener Haskell Interpreter der auf
fast allen Rechnern lauffähig ist.
Glasgow Haskell Compiler (GHC)
Ein selbst in Haskell geschriebener Compiler, der eine volle
Implementierung
von Haskell beeinhaltet und dessen Quellcode frei zur Verfügung
steht.
Glasgow Parallel Haskell
(GPH)
Eine Implementierung zur Erweiterung der Spracheiegenschaften von
Haskell
um die Realisierung von Parallelität zu ermöglichen.
nhc98
Ein Haskell 98 Compiler der von Niklas Röjemo entwickelt wurde.
HBI und
HBC (Chalmer's Haskell Interpreter und Compiler)
Ein von Lennart Augustsson entwickelter Haskell Interpreter / Compiler,
der Haskell 1.4 voll implementiert.
Für Standard ML:
Standard ML of New Jersey
Implementiert ab Version 110 Standard ML '97
Moscow ML
Ein Compiler der Standard ML implementiert
ML Kit
Ein Entwicklungskit das Standard ML '97 implementiert.
MLton
Ein Compiler für Standard ML ' 97
PolyML
Ein Compiler der ab Version 4.0 Standard ML '97 implementiert.