| Haskell | |
|---|---|
![]() |
|
| Basisdaten | |
| Paradigmen: | funktional, nicht-strikt, modular |
| Erscheinungsjahr: | 1990 |
| Entwickler: | Simon Peyton-Jones, Paul Hudak[1], Philip Wadler, et.al. |
| Aktuelle Version: | Haskell 98, Haskell Prime in Arbeit (1998) |
| Typisierung: | statisch, stark, Typabgleich |
| wichtige Implementierungen: | GHC, Hugs, NHC, JHC, Yhc |
| Dialekte: | Helium, Gofer |
| Einflüsse: | APL, LISP, Miranda, ML |
| Beeinflusste: | Scala, Cayenne, Clean |
| haskell.org | |
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. Die wichtigsten Implementierungen sind der Glasgow Haskell Compiler (GHC) und Hugs, ein Haskell-Interpreter.
Inhaltsverzeichnis |
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 aktuelle Version der Programmiersprache ist eine überarbeitete Variante des Haskell-98-Standards von 1999. Haskell ist die funktionale Sprache, an der zurzeit am meisten geforscht wird. Demzufolge sind die Sprachderivate zahlreich; dazu zählen Parallel Haskell, Distributed Haskell (ehemals Goffin), Eager Haskell, Eden, DNA-Haskell und sogar objektorientierte Varianten (Haskell++, O'Haskell, Mondrian). Des Weiteren diente Haskell beim Entwurf neuer Programmiersprachen als Vorlage. So wurde z.B. im Falle von Python die Lambda-Notation sowie Listenverarbeitungssyntax übernommen.
first x y = x quadrat x = x * x
map :: (a -> b) -> [a] -> [b]
map toUpper :: [Char] -> [Char]
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]
data Tree = Leaf Int | Branch Int Tree Tree
data Tag = Montag | Dienstag | Mittwoch | Donnerstag | Freitag | Samstag | Sonntag
deriving (Show, Eq, Ord, Ix, Enum)
IO.putStrLn :: String -> IO () getLine :: IO String
putStrLn gibt einen Text und einen Zeilenumbruch auf der Standardausgabe aus. Da es kein Ergebnis gibt, wird der leere Typ () als Rückgabetyp verwendet. getLine liest eine Textzeile von 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.Haskell unterscheidet Groß- und Kleinschreibung. Bezeichner, die mit einem Großbuchstaben beginnen, stehen für Typen und Konstruktoren. Bezeichner, die mit einem Kleinbuchstaben beginnen, stehen für Typvariablen, Funktionen und Parameter.
Haskell bietet eine Reihe von syntaktischen Besonderheiten. Diese sollen nicht darüber hinwegtäuschen, dass alles rein funktional erklärt ist.
Statt
readFile "eingabe.txt" >>= writeFile "ausgabe.txt"
oder
readFile "eingabe.txt" >>= (\inhalt -> writeFile "ausgabe.txt" inhalt)
kann man auch
do inhalt <- readFile "eingabe.txt"
writeFile "ausgabe.txt" inhalt
schreiben.
a + b == (+) a b a `div` b == div a b
[ x | x <- [1..], even x]
als Umschreibung für
filter even [1..]
fak :: Integer -> Integer fak 0 = 1 fak n = n * fak (n-1)
Zu Haskell gehört auch ein Modulsystem. Der Haskell-98-Standard definiert eine Grundmenge von Modulen[2], 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
In verschiedenen Modulen können Funktionen und Typen die gleichen Namen besitzen. Diese Bezeichner können unterschieden werden,
import Data.List(delete) x = delete 'a' "abc"
import qualified Data.List x = Data.List.delete 'a' "abc"
oder
import qualified Data.List as List x = List.delete 'a' "abc"
Ebenfalls möglich aber nicht empfohlen ist das Ausblenden von Bezeichnern beim Importieren mit hiding.
Eine elegante Definition der Fakultätsfunktion, die Haskells Notation für Listen benutzt:
fac :: Integer -> Integer fac n = product [1..n]
Eine einfache Implementierung der Fibonacci-Funktion:
fib :: Integer -> Integer fib 0 = 0 fib 1 = 1 fib n = fib (n - 2) + fib (n - 1)
Eine schnelle Implementierung der Folge:
fibs :: [Integer] fibs = 0 : 1 : (zipWith (+) fibs (tail fibs))
tail entfernt das erste Element aus einer Liste, zipWith kombiniert zwei Listen elementweise mithilfe einer weiteren Funktion (hier (+)). Die Definition entspricht einer Fixpunktgleichung. Dass die Definition stimmt, überprüft man am schnellsten, indem man sich vorstellt, dass fibs bereits fertig berechnet vorliegt. Als nächstes muss man sich noch überlegen, dass die Definition auch ausgewertet werden kann. Die ersten beiden Glieder von fibs sind unmittelbar klar: 0 und 1. Für das Berechnen jedes weiteren Gliedes muss aber nur auf bereits berechnete Glieder von fibs zurückgegriffen werden. Die Bedarfsauswertung führt dazu, dass die Folge fibs tatsächlich elementweise berechnet wird.
Man könnte auch sagen, dass fibs ein Fixpunkt der Funktion \xs -> 0 : 1 : (zipWith (+) xs (tail xs)) ist. Das wiederum lässt sich in Haskell direkt notieren als
fix (\xs -> 0 : 1 : (zipWith (+) xs (tail xs)))
Man kann auf diese Weise sehr elegant Differentialgleichungen bezüglich Potenzreihen oder Differenzengleichung bezüglich Zahlenfolgen formulieren und gleichzeitig lösen.
Angenommen, man möchte die Differentialgleichung y'(x) = f(x,y(x)) bezüglich y in Form einer Zeitreihe lösen, also einer Liste von Zahlen. Durch diese Diskretisierung wird die Differentialgleichung zur Differenzengleichung. Statt eines Integrals berechnen wir Partialsummen. Die folgende Funktion hat als Parameter die Integrationskonstante und eine Zahlenfolge.
integrate :: Num a => a -> [a] -> [a] integrate = scanl (+)
scanl akkumuliert die Werte einer Folge mit Hilfe einer anderen Funktion, hier (+), und gibt die Liste der Akkumulatorzustände zurück.
Damit kann man bereits das explizite Euler-Verfahren für die Schrittweite 1 implementieren. x0 und y0 sind hierbei die Anfangswerte. Der Apostroph hat keine eigenständige Bedeutung, er ist Teil des Namens y'.
eulerExplicit :: Num a => (a -> a -> a) -> a -> a -> [a]
eulerExplicit f x0 y0 =
let x = iterate (1+) x0
y = integrate y0 y'
y' = zipWith f x y
in y
Diese Funktionsdefinition besteht also im wesentlichen aus der Feststellung, dass y das Integral von y' mit Anfangswert y0 ist, (oder umgekehrt, y' die Ableitung von y) und aus der eigentlichen Differentialgleichung y' = zipWith f x y. Weil man hierbei den Algorithmus eher in der Form der Aufgabenstellung als in Form eines Lösungsweges notiert, spricht man hierbei von Deklarativer Programmierung.
Der Quicksortalgorithmus, formuliert in Haskell:
qsort :: Ord a => [a] -> [a] qsort [] = [] qsort (x:xs) = qsort [y | y <- xs, y < x] ++ [x] ++ qsort [y | y <- xs, y >= x]
Die erste Zeile definiert die Signatur von Quicksort. Die zweite Zeile gibt an, dass die Funktion auf eine leere Liste angewendet wieder eine leere Liste ergeben soll. Die dritte Zeile sortiert rekursiv nicht-leere Listen: das erste Element x wird als mittleres Element der Ergebnisliste verwendet. Davor werden sortiert alle kleineren, dahinter alle größeren Elemente eingeordnet. Listenbeschreibungen werden dazu verwendet, aus der Restliste xs alle kleineren bzw. größeren Elemente als x auszuwählen.
Wie man es von Quicksort erwartet, besitzt auch diese Implementierung eine mittlere asymptotische Laufzeit von
und eine Worst-Case-Laufzeit von O(n2). Im Gegensatz zur geläufigen Implementierung in einer imperativen Sprache arbeitet dieses qsort jedoch nicht in-place.