Go to the first, previous, next, last section, table of contents.


Module und Bibliotheken

Aufbau eines Haskell Programms

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.

Export

In der Exportliste können Einheiten aufgeführt werden, die in dem Modul definiert oder aus einem anderen Modul importiert werden.

Werte
durch Angabe des Namens,
Datentypen
in der Form `T(C1,...,Cn)': alle Konstruktoren werden aufgeführt, oder `T(...)': Abkürzung für die obige Schreibweise, oder `T': die Konstruktoren werden nicht exportiert,
Typsynonyme
in der Form `T(...)'
Typklassen
in der Form `C(f1,...,fn)': alle Operationen werden aufgeführt, oder `C(...)': Abkürzung für die obige Schreibweise,
Module
in der Form `M..': alle importierten Einheiten werden reexportiert. Beachte: auch das aktuelle Modul kann angegeben 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.

Import

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)
----------------------------------------------------------------------

Abstrakte Datentypen

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.

Bonner Haskell Bibliothek

Um das Rad nicht neu erfinden zu müssen, gibt es die Bonner Haskell Bibliothek (im Aufbau!).

Sortierfunktionen (`Sort')
Mengen (`OrdList', `SplaySet'):
Implementierung des Typs Menge und unzähliger Mengenoperationen,
Multimengen (`Bag'):
eine Multimenge kann Duplikate enthalten,
endliche Abbildungen (`OrdAssList', `Trie'):
auch bekannt unter den Namen `dictionary' oder `lookup table',
Parser (`CPSParser'):
für die systematische Konstruktion von `recursive descent' Parsern,
Pretty Printing (`Pretty'):
für textuelle Ausgabe mit Zeilenumbruch und Einrückung
Hilfsfunktionen (`Support', `IOSupport')

Programmentwicklung

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').

  1. Pro Modul eine Datei gleichen Namens anlegen (das Modul `Tree' steht in der Datei `Tree.lhs').
  2. Die Modulnamen und den Programmnamen in das `Makefile' eintragen:
    #-----------------------------------------------------------
    # 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
    
  3. Die Module von Hand übersetzen (dabei Modulabhängigkeiten beachten!):
        make Module1.o
        make Module2.o
        make Main.o
    
  4. Die Modulabhängigkeiten automatisch (!) zum `Makefile' hinzufügen lassen:
        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
    
  5. Das `Makefile' ist jetzt einsatzbereit:


Go to the first, previous, next, last section, table of contents.