0% fanden dieses Dokument nützlich (0 Abstimmungen)
20 Ansichten8 Seiten

Haskell

Das Dokument beschreibt Haskell als funktionale Programmiersprache. Es erläutert die Entwicklung von Haskell, die Unterschiede zu imperativen Sprachen, wichtige Eigenschaften wie die Typsicherheit und die Verwendung von Modulen.

Hochgeladen von

Alper Korkmaz
Copyright
© All Rights Reserved
Wir nehmen die Rechte an Inhalten ernst. Wenn Sie vermuten, dass dies Ihr Inhalt ist, beanspruchen Sie ihn hier.
Verfügbare Formate
Als PDF, TXT herunterladen oder online auf Scribd lesen
0% fanden dieses Dokument nützlich (0 Abstimmungen)
20 Ansichten8 Seiten

Haskell

Das Dokument beschreibt Haskell als funktionale Programmiersprache. Es erläutert die Entwicklung von Haskell, die Unterschiede zu imperativen Sprachen, wichtige Eigenschaften wie die Typsicherheit und die Verwendung von Modulen.

Hochgeladen von

Alper Korkmaz
Copyright
© All Rights Reserved
Wir nehmen die Rechte an Inhalten ernst. Wenn Sie vermuten, dass dies Ihr Inhalt ist, beanspruchen Sie ihn hier.
Verfügbare Formate
Als PDF, TXT herunterladen oder online auf Scribd lesen

Inhaltsverzeichnis

Haskell als eine funktionale Programmiersprache und Entwicklung ...................................................... 2


Entwicklung ......................................................................................................................................... 2
Unterschiede von Haskell ................................................................................................................... 2
Die Abstraktionsebene ....................................................................................................................... 2
Eigenschaften ...................................................................................................................................... 3
Typsystem ........................................................................................................................................... 4
Syntax .................................................................................................................................................. 6
Programmierung ............................................................................................................................. 7
Module ............................................................................................................................................ 7
Quicksort ......................................................................................................................................... 8
Haskell als eine funktionale Programmiersprache und Entwicklung

Haskell ist eine rein funktionale Programmiersprache, benannt nach dem US-amerikanischen
Mathematiker Haskell Brooks Curry, dessen Arbeiten zur mathematischen Logik eine
Grundlage funktionaler Programmiersprachen bilden. Haskell basiert auf dem Lambda-Kalkül,
weshalb auch der griechische Buchstabe Lambda als Logo verwendet wird. Das heißt,
Ausgehend von einem mathematischen Term, wie beispielsweise 𝑥 + 2, lässt sich eine
Funktion bilden, die 𝑥 auf 𝑥 + 2 abbildet. Man schreibt auch 𝑥 → 𝑥 + 2. Im Lambda-Kalkül
würde man statt 𝑥 → 𝑥 + 2 den Term 𝜆𝑥, 𝑥 + 2 schreiben.

Entwicklung

Gegen Ende der 1980er Jahre gab es bereits einige funktionale Programmiersprachen.
Um der Wissenschaft eine einheitliche Forschungs- und Entwicklungsbasis
bereitzustellen, sollte eine standardisierte und moderne Sprache die funktionale
Programmierung vereinheitlichen. Zunächst wollte man dazu Miranda als
Ausgangspunkt benutzen; doch deren Entwickler waren daran nicht interessiert. So
wurde 1990 Haskell 1.0 veröffentlicht.

Die Sprachderivate von Haskell sind zahlreich; dazu zählen Parallel Haskell, Distributed
Haskell (ehemals Goffin), Eager Haskell, Eden mit einem neuen Ansatz zum parallelen
Programmieren und Bedarfsauswertung, DNA-Haskell und sogar objektorientierte
Varianten (Haskell++, O’Haskell, Mondrian). Des Weiteren diente Haskell beim Entwurf
neuer Programmiersprachen als Vorlage. So wurden beispielsweise im Falle von
Python die Lambda-Notation sowie Listenverarbeitungssyntax übernommen.

Unterschiede von Haskell

Programmiersprachen wie C/C++/Java/Python werden als imperative Programmiersprachen


bezeichnet, weil sie aus Abfolgen von Aktionen bestehen. Der Programmierer sagt dem
Computer ganz explizit, wie er eine Aufgabe Schritt für Schritt ausführen soll. Funktionale
Programmiersprachen funktionieren anders. Anstatt Aktionen in einer Abfolge auszuführen,
werten sie Ausdrücke aus.

Die Abstraktionsebene

Es gibt zwei Bereiche, die für die Programmierung eines Computers von grundlegender
Bedeutung sind - die Ressourcenverwaltung und die Ablaufsteuerung. Die meisten neuen
Sprachen (sowohl imperative als auch funktionale) haben die Garbage Collection
implementiert, um die Ressourcenverwaltung aus dem Problem zu entfernen und dem
Programmierer die Möglichkeit zu geben, sich auf den Algorithmus zu konzentrieren, anstatt
sich mit der Buchhaltung der Speicherzuweisung zu beschäftigen. Auch die Sequenzierung hat
eine gewisse Abstraktion erfahren, wenn auch nicht annähernd in demselben Ausmaß.
Imperative Sprachen haben dies durch die Einführung neuer Schlüsselwörter und
Standardbibliotheken erreicht. Die meisten imperativen Sprachen verfügen beispielsweise
über eine spezielle Syntax für die Konstruktion mehrerer leicht unterschiedlicher Schleifen, so
dass Sie nicht mehr alle Aufgaben zur Verwaltung dieser Schleifen selbst erledigen müssen.
Aber imperative Sprachen basieren auf dem Konzept der Sequenzierung - sie können diesem
nie ganz entkommen. Die einzige Möglichkeit, den Abstraktionsgrad einer imperativen
Sprache im Bereich der Sequenzierung zu erhöhen, besteht darin, mehr Schlüsselwörter oder
Standardfunktionen einzuführen, wodurch die Sprache unübersichtlich wird. Diese enge
Beziehung zwischen imperativen Sprachen und der Aufgabe, Befehle für die Ausführung durch
den Prozessor zu sequenzieren, bedeutet, dass imperative Sprachen nie über die Aufgabe der
Sequenzierung hinausgehen können und daher nie die gleiche Abstraktionsebene erreichen
können wie funktionale Programmiersprachen.
In Haskell ist die Aufgabe der Sequenzierung weggefallen. Es ist nur wichtig, was das
Programm berechnen soll, nicht wie oder wann es berechnet wird. Das macht Haskell zu einer
flexibleren und einfach zu verwendenden Sprache. Haskell neigt dazu, Teil der Lösung eines
Problems zu sein, nicht ein Teil des Problems selbst.
Da funktionale Sprachen intuitiver sind und mehr und einfachere Möglichkeiten bieten, die
Aufgabe zu erledigen, sind funktionale Programme in der Regel kürzer (in der Regel 2 bis 10
Mal kürzer). Die Semantik ist meist viel näher am Problem als eine imperative Version, was
die Überprüfung der Korrektheit einer Funktion erleichtert. Außerdem lässt Haskell keine
Seiteneffekte zu, was zu weniger Fehlern führt. Daher sind Haskell-Programme einfacher zu
schreiben, robuster und leichter zu warten.
Haskell verfügt über Werkzeuge zur Abstraktion von Daten. Wir können nicht wirklich darauf
eingehen, ohne zuerst das Modulsystem und die Funktionsweise von abstrakten Datentypen
(ADT) in Haskell durchzugehen, was den Rahmen dieses Aufsatzes bei weitem sprengen
würde. Wir werden uns daher mit einer kurzen Beschreibung der Funktionsweise von ADTs
und Polymorphismus in Haskell begnügen.
Datenkapselung wird in Haskell dadurch erreicht, dass jeder Datentyp in einem separaten
Modul deklariert wird und aus diesem Modul nur die Schnittstelle exportiert wird. Intern kann
es eine Vielzahl von Funktionen geben, die die eigentlichen Daten berühren, aber die
Schnittstelle ist alles, was von außerhalb des Moduls sichtbar ist. Beachten Sie, dass der
Datentyp und die Funktionen, die auf den Datentyp wirken, nicht in einem "Objekt" gruppiert
sind, sondern (typischerweise) in demselben Modul, so dass Sie sich dafür entscheiden können,
nur bestimmte Funktionen zu exportieren (und nicht die Konstruktoren für den Datentyp), so
dass diese Funktionen die einzige Möglichkeit sind, den Datentyp zu manipulieren - und die
Implementierung vor der Schnittstelle "verstecken".

Eigenschaften

a. Haskell ist eine rein funktionale Programmiersprache. Funktionen geben nur Werte
zurück, ändern aber nicht den Zustand eines Programms (d. h. Funktionen haben keine
Nebeneffekte). Das Ergebnis einer Funktion hängt deshalb nur von den
Eingangsparametern ab, und nicht davon, wann oder wie oft die Funktion aufgerufen
wird.
b. Es gibt keine imperativen Sprachkonstrukte. Durch Monaden ist es möglich, Ein- und
Ausgabeoperationen und zustandsabhängige Berechnungen wie Zufallsgeneratoren
rein funktional zu behandeln.
c. Es gibt keine Operationen, die einen Variablenwert verändern. So gibt es auch keine
Unterscheidung zwischen Variablen und Konstanten und man braucht keine const-
Attribute oder Literal-Makros wie in C++ oder in C.
d. Zwischen Identität und Gleichwertigkeit von Objekten wird nicht unterschieden.
e. Da Nebeneffekte fehlen, sind Programmbeweise beträchtlich einfacher.
f. Haskell ist nicht-strikt. Es werden nur Ausdrücke ausgewertet, die für die Berechnung
des Ergebnisses gebraucht werden.

Typsystem

- Haskell ist stark typisiert. Es wird also zum Beispiel streng zwischen Wahrheitswerten,
Zeichen, ganzen Zahlen, Gleitkommazahlen und Funktionen von und zu verschiedenen
Typen unterschieden.
- Haskell erlaubt Typvariablen. Damit können Funktionen sehr allgemein formuliert
werden. Wird eine allgemeingehaltene Funktion für bestimmte Typen verwendet,
werden automatisch die Typen abgeglichen.
Die Funktion map wendet eine beliebige Funktion auf die Elemente einer Liste an. Ihr
Typ wird so angegeben:

map :: (a -> b) -> [a] -> [b]

Wird map etwa mit der speziellen Funktion toUpper vom Typ Char -> Char
aufgerufen, ergibt der Typabgleich

map toUpper :: [Char] -> [Char]

- Haskell ist von der Grundidee her statisch typisiert, obwohl es auch Erweiterungen für
dynamische Typen gibt. Das bedeutet, dass für die meisten Berechnungen die Typen bereits
zum Zeitpunkt der Programmübersetzung feststehen. Dies deckt viele „offensichtliche“ Fehler
noch vor Ausführung des Programms auf.
- Haskell unterstützt Funktionen höherer Ordnung (Funktionale). Das sind Funktionen, die
Funktionen als Eingabeparameter bzw. Funktionen als Ergebnis haben. Ein Beispiel ist die map-
Funktion, die eine Funktion f auf jedes Element eines Datentyps anwendet.

map :: (a -> b) -> [a] -> [b]


map f [] = []
map f (x:xs) = f x : map f xs

map quadrat [1,2,3] = [quadrat 1, quadrat 2, quadrat 3] = [1,4,9]

- Funktionen erlauben Currying. Während man in anderen Sprachen Tupel als


Argumente an Funktionen übergibt, also Funktionstypen der Form (a, b) -> c
verwendet, ist in Haskell die Curry-Form a -> b -> c üblicher. Damit wird die
partielle Auswertung von Funktionen bequem möglich. Der Ausdruck map toUpper
ist beispielsweise eine teilweise Auswertung von map, denn er beschreibt eine
Funktion, nämlich die Funktion, welche alle Kleinbuchstaben einer Liste in
Großbuchstaben verwandelt.
- Haskell erlaubt benutzerdefinierte Datentypen. Diese algebraischen Datentypen
werden mit Hilfe von Datenkonstruktoren definiert.
data Tree = Leaf Int | Branch Int Tree Tree

Das Beispiel zeigt die Datenstruktur eines mit ganzen Zahlen beschrifteten binären
Baumes. Solch ein Baum Tree besteht entweder aus einem Blatt (Leaf Int) oder einer
Verzweigung (Branch Int t1 t2), wobei t1 und t2 die Teilbäume darstellen, die
wiederum die Struktur Tree haben. Zur Definition dieser Datenstruktur wurde sowohl
der einstellige Konstruktor Leaf als auch der dreistellige Konstruktor Branch
verwendet. Datentypen mit mehreren ausschließlich parameterlosen Konstruktoren
können als Aufzählungen eingesetzt werden.

data Tag = Montag | Dienstag | Mittwoch | Donnerstag | Freitag | Samstag |


Sonntag
deriving (Show, Eq, Ord, Ix, Enum)

- Haskell unterstützt Typenklassen. Mit Typenklassen lassen sich Typen


zusammenfassen, welche eine bestimmte Menge an Operationen unterstützen. In
Signaturen von Funktionen dürfen als Abstufung zwischen festen Typen wie Char und
freien Typvariablen auch noch Typvariablen mit Einschränkung auf bestimmte Klassen
verwendet werden.
Alle Ausprägungen einer Methode der Typklasse tragen den gleichen Namen. In
gewisser Weise entsprechen Typklassen also dem Überladen von Funktionen. Der
gleiche Funktionsname steht also abhängig vom Typ für verschiedene Funktionen. Zum
Beispiel ist mit der ==-Methode der Klasse Eq der Vergleich sowohl zweier Zahlen als
auch zweier Texte möglich. Trotzdem arbeitet der Gleichheitstest je nach Argumenttyp
anders.
- In Haskell haben Ein- und Ausgabefunktionen einen speziellen Typkonstruktor namens IO.

putStrLn :: String -> IO ()


getLine :: IO String

putStrLn gibt einen Text und einen Zeilenumbruch auf der Standardausgabe aus. Da es kein
informationstragendes Ergebnis gibt, wird der Einheitstyp () als Rückgabetyp verwendet.
getLine liest eine Textzeile der Standardeingabe. Der IO-Typkonstruktor stellt sicher, dass man
den Nutzern der Funktion offenlegen muss, dass die Ergebnisse durch Ein-/Ausgabe gewonnen
wurden. Diese strenge Handhabung ermuntert Haskell-Programmierer zur klaren Trennung
von Ein- und Ausgabe und anderen Teilen eines Programms. Der größte Teil eines Haskell-
Programms besteht in der Regel aus Funktionen ohne Ein- und Ausgabe. Man kann IO-Typen
natürlich auch in andere Typen einbetten und so zum Beispiel einen speziellen IO-Typ
definieren, der nur Eingaben erlaubt.
Syntax

Haskell unterscheidet Groß- und Kleinschreibung. Bezeichner, die mit einem Großbuchstaben
beginnen, stehen für Typ- und Wertkonstruktoren. Bezeichner, die mit einem
Kleinbuchstaben beginnen, stehen für Typvariablen, Funktionen und Parameter.

Der Umgang mit Leerzeichen und Zeilenumbrüchen geschieht in Anlehnung an das intuitive
Verständnis von mathematischer Notation, bei Zeilenumbrüchen muss lediglich eine
Einrückung beliebiger Tiefe geschehen, damit der Zusammenhang nicht verlorengeht. So ist
der Ausdruck

fun a b = a*b

völlig gleichwertig zu

fun a b= a *
b

- Die do-Notation verleiht Berechnungen mit Monaden das Aussehen von imperativen
Programmen.
Statt

readFile "[Link]" >>= writeFile "[Link]"

oder

readFile "[Link]" >>= (\inhalt -> writeFile "[Link]"


inhalt)

kann man auch

do inhalt <- readFile "[Link]"


writeFile "[Link]" inhalt
- Sowohl symbolische Bezeichner (bestehend etwa aus +, -, *, /, >, < ) als auch
alphanumerische Bezeichner (Buchstaben, Ziffern und Apostroph) können für
Funktionsnamen verwendet werden und sowohl als Infix-Operatoren als auch in
Präfixschreibweise eingesetzt werden. Es gilt beispielsweise
a + b = (+) a b
a `div` b = div a b

- Haskell erlaubt spezielle Notationen bei der Listenverarbeitung. So können unter


anderem Zahlenfolgen mit zwei Punkten (..) angedeutet werden:
[0..5] = [0,1,2,3,4,5]
['a'..'e'] = ['a','b','c','d','e'] = "abcde"
[0,2..10] = [0,2,4,6,8,10]
[1..] = [1,2,3 usw.]
[10,20..] = [10,20,30 usw.]

Wird kein Endwert angegeben, dann wird eine unendliche Liste erzeugt.
Des Weiteren ist eine Notation erlaubt, genannt „list comprehension“, die an die
mathematische Schreibweise für Mengendefinitionen angelehnt ist. In folgendem
Beispiel wird aus der Folge der positiven natürlichen Zahlen die Folge der geraden
Zahlen extrahiert.
[ x | x <- [1..], even x]

als Umschreibung für


do
x <- [1..]
guard $ even x
return x

Im Allgemeinen kann hinter dem senkrechten Strich eine beliebige nichtleere Folge aus
Generatoren (pat <- xs), Prädikaten (Ausdrücken mit dem Typ Bool) und let-Bindungen
angegeben werden. Insbesondere ist es möglich, überhaupt keine Generatoren zu
verwenden. Der Ausdruck

[x | odd x]

nimmt je nach Wert von x, welches als bereits definiert vorausgesetzt wird, den Wert [] oder
[x] an.

Programmierung

Haskell erlaubt Mustervergleiche (pattern matching). So nennt man die Verwendung von
Konstruktortermen als formale Parameter. Dabei sind die Parameterterme die Muster der
Funktionsargumente.

fak :: Integer -> Integer


fak 0 = 1
fak n = n * fak (n-1)

Die Funktion fak berechnet die Fakultät einer Zahl. 0 und n sind dabei die Muster, von denen
die Ergebnisbestimmung abhängt. Für Zahlen größer als 0 greift nur das Muster n, so dass
zweitere Alternative verwendet wird. Diese errechnet das Ergebnis durch n * fak (n-1), wobei
sie sich, solange (n-1) > 0 ist, rekursiv selbst aufruft, bis sie bei 0 ankommt. Dort greift dann
das Muster 0, so dass erstere Alternative verwendet wird, welches die Rekursion sauber
abschließt, 1 zurückgibt und die Rücksprungkette einleitet.

Module

Zu Haskell gehört auch ein Modulsystem. Der Haskell-98-Standard definiert eine Grundmenge
von Modulen, die ein standardkonformes Haskell-System zur Verfügung stellen muss.
Beispielsweise ein Modul, welches Ein- und Ausgabe-Funktionen bereitstellt oder ein Modul,
welches Funktionen auf Listen implementiert. Um Module nutzen zu können, muss man sie
importieren. Dies geschieht mithilfe des import-Befehls.
import List
import Maybe

Quicksort

Der Quicksort-Algorithmus, formuliert in Haskell:

qsort :: Ord a => [a] -> [a]


qsort [] = []
qsort (x:xs) = qsort kleinergl ++ [x] ++ qsort groesser
where
kleinergl = [y | y <- xs, y <= x]
groesser = [y | y <- xs, y > x]

Das könnte Ihnen auch gefallen