Grundlegende Konzepte


... [Seminar "Haskell"] ... [Inhaltsübersicht] ... [zurück] ... [weiter] ...

Grundlagen der Haskell-Programmierung



Der Interpreter als Rechner

Eine wesentliche Aufgabe des Computers in der funktionalen Programmierung ist das Auswerten von Ausdrücken in Form von Rechnen. Ausdrücke können im Haskell-Interpreter interaktiv in sogenannten Sessions ausgewertet werden. Eine Session setzt sich zusammen aus einer Benutzereingabe und der zugehörigen Rückmeldung des Computers. Der Benutzer übergibt dem Interpreter direkt am Eingabe-Prompt einen mit einem Zeilenumbruch abgeschlossenen Ausdruck und der Interpreter zeigt das Ergebnis der Auswertung dieses Ausdruckes an:

Prelude> 3 + 4
7
Prelude>

Damit ist die Session abgeschlossen und der Benutzer kann durch die erneute Eingabe eines Ausdruckes eine neue, von der vorangegangenen vollkommen unabhängige Session starten:

Prelude> 'a' == 'b'
False
Prelude>

Die im Beispiel verwendete Addition gehört zur Grundausstattung des Interpreters, in der alle grundlegenden arithmetischen Operationen definiert sind. Viele andere gebräuchliche Funktionen, wie z.B. die Prüfung auf Gleichheit, werden vom Prelude-Modul bereitgestellt, das beim Start des Interpreters automatisch mit geladen wird und dessen Definitionen damit stets zur Verfügung stehen.


Eigene Definitionen

In der Regel sind jedoch die an den Programmierer herangetragenen Probleme sehr viel komplexer als die obigen Beispielausdrücke. Zur Auswertung der zur Problemlösung entwickelten Funktionen reichen die Standardfunktionalitäten in den meisten Fällen nicht aus. Darüber hinaus entspricht die oben beschriebende interaktive Nutzung des Interpreters, zum Beipiel im Hinblick auf die Wiederverwendbarkeit von Teilausdrücken, nicht dem gängigen Verständnis von Programmierung.

Einen wesentlichen Aspekt der funktionalen Programmierung bildet daher die Erstellung eigener Definitionen.

Eine Definition besteht aus einem Namen und einem Ausdruck, die durch ein Gleichheitszeichen getrennt sind:

name = ausdruck

Der Interpreter verarbeitet die Definition wie eine Ersetzungsregel. Jedes Vorkommen von name in einem auszuwertenden Ausdruck wird durch den ausdruck auf der rechten Seite der Definition ersetzt.
Optional kann jede Definition durch eine Typdefinition ergänzt werden (für nähere Informationen: siehe "Typen"). Ein Typdefinition hat die Syntax:

name :: Typ

Es wird zwischen Wertdefinitionen und Funktionsdefinitionen unterschieden.

Beispiel für eine Wertdefinition:
pi :: Float
pi = 3.14159
Beispiel für eine Funktionsdefinition:
square :: Float -> Float
square x = x * x

Um dem Interpreter die eigenen Definitionen bekannt zu machen, werden die Definitionen in einem Modul gespeichert, das dann in den Interpreter geladen wird. Die obigen Beispieldefinitionen werden wie folgt in einer Datei Test.hs gespeichert:

module Test where

pi :: Float
pi = 3.14159

square :: Float -> Float
square x = x * x
Das Modul Test.hs wird in den Interpreter geladen:
Prelude> :l Test.hs
Reading file "Test.hs":

Hugs session for:
C:\Programme\Hugs98\lib\Prelude.hs
Test.hs
Test>
Die Definitionen können sodann in den Sessions verwendet werden:

Test> pi
3.14159
Test>

Test> square pi
9.86959
Test>


Gross- und Kleinschreibung

Konventionen bezüglich der Schreibweise von Bezeichnern, Typen etc. existieren in vielen Programmiersprachen. Haskell aber geht über den üblichen Rahmen einer Konvention hinaus. Die Schreibweise ist in der Sprachdefinition verankert, so dass von der Schreibweise eines Wortes zwingend auf seine semantische Bedeutung geschlossen werden kann: Funktionsnamen und Wertenamen beginnen mit eínem Kleinbuchstaben, während Typen mit einem Grossbuchstaben beginnen. Eine Funktionsdefinition mit dem Namen Foo wird vom Compiler nicht akzeptiert.

Darüber hinaus wird im gesamten Namen Gross- und Kleinschreibung unterschieden. foo, fOo und fOO sind drei verschiedene Funktionen.


Layout

Haskell benutzt das Layout, um den Code zu strukturieren und so die sonst üblichen Semikolons und geschwungenen Klammern einzusparen. Einrückungen dienen also nicht nur der Lesbarkeit des Codes, sondern haben eine Bedeutung!

f x = case x of
    0 -> 1
    1 -> 23
    2 -> 42
    _ -> -1

Eine öffnende Klammer steht implizit nach den Schlüsselwörtern where, let, do und of. Alle Zeilen, die gleich weit eingerückt sind wie die Anweisung direkt nach einem solchen Schlüsselwort, werden implizit durch ein Semikolon von der vorigen Zeile getrennt.Vor der ersten weniger eingerückten Zeile steht implizit eine schliessende Klammer.
Für den obigen Case-Verteiler ergibt sich die folgende explizite Schreibweise:

f x = case x of {
    0 -> 1;
    1 -> 23;
    2 -> 42;
    _ -> -1 }
Werden alle Klammern und Semikolons explizit notiert, so kann das Layout frei gewählt werden:
f x = case x of { 0 -> 1 ; 1 -> 23 ; 2 -> 42 ; _ -> -1 }

Kommentare

In Haskell stehen zwei Arten von Kommentaren zur Verfügung.

Zeilenkommentare beginnen mit -- und erstrecken sich bis zum Ende der Zeile.

-- Dies ist ein Zeilenkommentar

Blockkommentare können über mehrere Zeilen gehen. Sie beginnen mit {- und enden mit -}.
Blockkommentare können verschachtelt auftreten. Ein einmal mit {- begonnener Kommentar ist damit nicht beim ersten Auftreten eines -} beendet, sondern beim korrespondierenden -}.

{- Blockkommentare können {- auch
verschachtelt -} auftreten -}


... [Seminar "Haskell"] ... [Inhaltsübersicht] ... [zurück] ... [weiter] ...          top of the page