Zur Erinnerung: Ein Haskell Programm besteht aus einem oder mehreren Modulen.
Ein Modul definiert Werte, Datentypen, Typsynonyme und Typklassen. Diese Einheiten können exportiert oder von anderen Modulen importiert werden.
Ein Modul hat die folgende syntaktische Form.
----------------------------------------------------------------------
module <modid> (<export1>, ..., <exportk>)
where {
<impdecl1>; ...; <impdeclm>;
<topdecl1>; ...; <topdecln>
}
----------------------------------------------------------------------
Wird der Modulkopf nicht angegeben (so wie bisher), wird implizit als Modulkopf `module Main where' eingesetzt.
In der Exportliste können Einheiten aufgeführt werden, die in dem Modul definiert oder aus einem anderen Modul importiert werden.
Beispiel: polymorphe Binärbäume als Modul.
---------------------------------------------------------------------- > module Tree ( Tree(Empty, Node), > insert, > delete, ... ) > where > data Tree lab = Empty | Node (Tree lab) lab (Tree lab) > deriving (Eq, Ord, Text) > > insert :: (Ord lab) => lab -> Tree lab -> Tree lab > delete :: (Ord lab) => lab -> Tree lab -> Tree lab > join :: IntTree -> IntTree -> IntTree > split :: IntTree -> Int -> IntTree -> (Int, IntTree) > ... ----------------------------------------------------------------------
Beachte: `join' und `split' sind nur lokal sichtbar. Beachte: die Instanzdeklarationen `instance (Eq a) => Eq (Tree a)' etc. werden automatisch exportiert.
Importdeklarationen können zwei verschiedene Formen haben.
----------------------------------------------------------------------
<impdecl> --> import <modid> [ <impspec> ]
<impdecl> --> ( <import1>, ..., <importn> )
| hiding ( <import1>, ..., <importn> )
----------------------------------------------------------------------
Die zu importierenden Einheiten werden wie in der Exportliste angegeben. Klar: nur das kann importiert werden, was auch exportiert wird.
Beispiel: Wir verwenden Binärbäume um `Splay Trees' zu implementieren.
---------------------------------------------------------------------- > module Splay ( splay, join ) > where > > import Tree ( Tree(Empty, Node) ) > > splay :: (a -> Ordering) -> Tree a -> (Ordering, Tree a) > join :: Tree a -> Tree a -> Tree a > ... ----------------------------------------------------------------------
Beachte: `Tree' wird nicht reexportiert. Alle Module, die `Splay' verwenden, müssen `irgendwie' auch `Tree' importieren. Sonst sind `splay' und `join' `Typwaisen' `;-)'.
Was tun, wenn es knallt? Werden zwei verschiedene Einheiten gleichen Namens importiert, gibt es eine Namenskollision. [Wie gesagt, Namen zu erfinden ist schwer.]
---------------------------------------------------------------------- > import List ( insert ) > import Tree ( insert ) ----------------------------------------------------------------------
Mit Hilfe des Zusatzes `renaming' lassen sich Einheiten umbenennen.
---------------------------------------------------------------------- > import List ( insert ) > import Tree ( insert ) > renaming (insert to insertTree) ----------------------------------------------------------------------
Mit Hilfe des Modulsystems lassen sich abstrakte Datentypen definieren. Im Unterschied zu normalen Datentypen ist die Repräsentation eines abstrakten Datentyps unbekannt: ein abstrakter Typ definiert sich über sein Verhalten, über die Menge der auf dem Typ definierten Operationen.
---------------------------------------------------------------------- > module Set ( Set, empty, add, ... ) > where > import Tree > > data Set a = Set (Tree a) > empty :: Set a > empty = Set Empty > add :: a -> Set a -> Set a > add a (Set x) = Set (insert a x) > ... ----------------------------------------------------------------------
Daß Mengen durch Binärbäume repräsentiert werden, ist außerhalb des Moduls nicht sichtbar. Vorteil: Die Repräsentation läßt sich problemlos ändern.
Um das Rad nicht neu erfinden zu müssen, gibt es die Bonner Haskell Bibliothek (im Aufbau!).
Klar: Eine größere Programmieraufgabe sollte man als Sammlung von Modulen realisieren, die einzeln entwickelt und getestet werden können.
Klar: Um die Durchlaufzeiten bei der Programmentwicklung (Modifikation -- Übersetzung -- Test) möglichst klein zu halten, sollte man `make' verwenden.
Kochrezept für Haskell: wir modifizieren ein generisches `Makefile' (das `Makefile' findet man unter `http://www.informatik.uni-bonn.de/~ralf/Make').
#-----------------------------------------------------------
# Global definitions
HC = ghc
HCFLAGS = -fhaskell-1.3 -i/home/III/a/ralf/Haskell/Library \
-L/home/III/a/ralf/Haskell/Library -lbn
SRCS = Main.lhs \
Module1.lhs \
Module2.lhs
OBJS = Main.o \
Module1.o \
Module2.o
MAIN = main
make Module1.o
make Module2.o
make Main.o
make depend
Danach endet das `Makefile' mit:
# DO NOT DELETE: Beginning of Haskell dependencies Main.o : Main.lhs Main.o : ./Module2.hi Module1.o : Module1.lhs Module2.o : Module2.lhs Module2.o : ./Module1.hi # DO NOT DELETE: End of Haskell dependencies
----------------------------------------------------------------------
make <program>
----------------------------------------------------------------------
----------------------------------------------------------------------
make depend
----------------------------------------------------------------------
----------------------------------------------------------------------
make clean
----------------------------------------------------------------------
Go to the first, previous, next, last section, table of contents.