Haskell

Lesezeit: 10 Minuten

Haskell ist eine rein funktionale Programmiersprache, die nach dem US-amerikanischen Mathematiker Haskell Brooks Curry benannt ist.

Gegen Ende der 1980er Jahre wuchs das Interesse an funktionalen Programmiersprachen. Als Vorreiter unter den gut ein Dutzend bereits vorhandenen Kandidaten galt die 1985 erschienene Sprache Miranda, die jedoch proprietär war. Zu dem Zeitpunkt gab es Bestrebungen in der Wissenschaft, einen offen Standard für rein funktionale Sprachen zu schaffen. Die 1987 in Portland, Oregon abgehaltene Conference on Functional Programming Languages and Computer Architecture (FPCA ’87), auf der sich die Ideen konkretisierten, kann man als die Geburtsstätte von Haskell bezeichnen.

Im Jahr 1990 erschien Haskell 1.0. Es folgten weitere Versionen bis Haskell 1.4 und dann im Jahr 1997 der Sprachstandard Haskell 98. Die Arbeiten an der zum Zeitpunkt dieses Beitrags noch aktuellen Revision Haskell 2010 begannen im Jahr 2006 und wurden im Juli 2010 abschließend veröffentlicht.

An der ursprünglichen Entwicklung und am Sprachdesign waren weit mehr als ein Dutzend Personen beteiligt, darunter Simon Peyton Jones, John Hughes, John Launchbury, Paul Raymond Hudak, Philip Wadler und Erik Meijer.

Alleinstellungsmerkmale

Haskell ist die rein funktionale Programmiersprache schlechthin. Jegliche Ein- und Ausgabe wird in Haskell als unrein (impure) betrachtet und im Datentyp IO gekapselt. Haskell kennt (mit Ausnahmen) keine Variablen und Konstanten, nur Funktionen. Außerhalb des IO-Datentypes gibt es keine Seiteneffekte und es gilt referenzielle Transparenz. Haskell-Code kann oftmals sehr kompakt und auf den Punkt gebracht geschrieben werden, zum einen wegen seiner funktionalen Natur, zum anderen wegen seiner gut durchdachten Sprachkonstrukte wie dem Pattern Matching.

Als Sprache mit Bedarfsauswertung (lazy evaluation) lassen sich in Haskell unendlich große Datenmengen und -folgen abbilden.

Einsatzbereiche

Haskell kommt in verschiedensten Branchen zum Einsatz, darunter Compliance, Finanzen, IT-Security, Logistik, Marketing und Wissenschaft. Die Sprache wird unter anderem bei Atos, Barclays, BNP, Caribou, Decathlon, Facebook/Meta, GitHub, JP Morgan, Kaspersky Lab, Klarna, Microsoft, Oracle Labs, Target Corporation, Tesla, Wire Swiss und Zalando eingesetzt.

Bekannte Haskell-Projekte sind die Bibliothek semantic zur Quellcode-Analyse von GitHub, die Projekte Sigma, Glean und Haxl von Meta, der Dokumentenparser Pandoc, der Window Manager xmonad, die Versionsverwaltung darcs, das Programm git-annex zur Verwaltung großer Dateien mit Git, die domänenspezifische Sprache für kryptographische Algorithmen cryptol und das Live-Coding-Musikprogramm Tidal Cycles.

Die multinationale Bank Standard Chartered hat sogar einen eigenen Compiler, Mu, als strikten Dialekt von Haskell im Einsatz.

Pros

Haskell hat eine sehr gut durchdachte und konsequente Syntax. Sie ist kompakt und ausdrucksstark und Vorbild für viele Programmiersprachen, z.B. JavaScript, Java, Perl, PHP, Python und Scala. Haskell zu lernen stellt eine Erweiterung für den eigenen Horizont dar. Ich habe mich, nachdem ich Haskell gelernt habe, in anderen Sprachen oft dabei erwischt, Probleme funktional lösen zu wollen anstatt z.B. eine for-Schleife mit lokalen Variablen zu verwenden.

Haskell bietet viele interessante Features, wie Pattern Matching, Lambda-Ausdrücke und List Comprehension. Das folgende Beispiel zeigt die Anwendung von Pattern Matching:

checkList :: [a] -> String
checkList [] = "empty"
checkList [x] = "1 element"
checkList [x,y] = "2 elements"
checkList _ = "more than 2 elements"

Die Funktion checkList gibt für eine Liste [] vom Typ a an, ob diese leer ist oder ein oder zwei oder mehr Elemente beinhaltet. Pattern Matching stellt hier eine elegantere Alternative zu der in der imperativen Welt populären ifelse– bzw. switchcase-Verschachtelung dar, funktioniert aber nicht für beliebige Bedingungen.

isPrime :: Int -> Bool
isPrime 2 = True
isPrime n
    | n < 2 = False
    |  [x | x <- [2 .. (n-1))], mod n x == 0 ]) == [] = True
    | otherwise = False

Diese nicht-optimierte Primzahlfunktion gibt für 2 den Wert True und für alle Zahlen kleiner 2 den Wert False zurück. Die Pipe | repräsentiert einen Guard, diese werden von oben nach unten abgearbeitet. In der Zeile 5 kommt List Comprehension zur Anwendung. Der Ausdruck in eckigen Klammern liefert eine Liste von x zurück wobei x alle Zahlen zwischen 2 und dem Argument minus 1 sind, für die mod n x == 0 gilt, durch die n also teilbar ist. Ist diese Liste leer, so handelt es sich um eine Primzahl und es wird True zurückgegeben. otherwise ist ein Pattern, das immer zutrifft. Es handelt sich bei der letzten Zeile also um ein catch all.

Sowohl das Pattern Matching allgemein als auch Guards im Speziellen sind meiner Meinung nach eine sehr gute Möglichkeit, um auszudrücken, wie eine Funktion auf bestimmte Eingabewerte, gerade auf die Randfälle, reagiert. Zu einem gewissen Grad hat Haskell damit aus meiner Sicht ein integriertes Design by Contract.

Eines meiner Lieblingsattribute von Haskell ist die Bedarfsauswertung (lazy evaluation). Haskell kann unendliche Datenfolgen darstellen, da Berechnungen immer erst bei Bedarf stattfinden. Es ist etwa völlig legitim mit [1..] zu arbeiten, was eine Zahlenfolge von 1 bis unendlich repräsentiert. Das folgende (nicht-optimierte) Beispiel zeigt, wie hiermit alle Fibonacci-Zahlen kleiner einer Million ausgegeben werden:

fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

main :: IO ()
main = do print (sum (takeWhile (<1000000) [fib x  | x <- [1..]]))

Bei Haskell handelt es sich um eine rein funktionale Programmiersprache. D.h. es gibt erstmal keine Variablen, keinen Zustand und keine Seiteneffekte. Damit gilt referenzielle Transparenz und für die gleichen Eingabewerte gibt eine Funktion immer den gleichen Ausgabewert zurück. So zumindest verhält es sich in der reinen Welt. Da Haskell auch um Zustand nicht herumkommt, werden alle Seiteneffekt-behafteten Instruktionen wie Datenbankzugriffe und Bildschirmausgaben in einer IO-Monade gekapselt. Funktionen mit IO sind unrein (impure) und geben immer einen IO-Wert zurück. Im obigen Beispiel ist main eine unreine Funktion, die nichts () zurückgibt und via Aufruf von print als Seiteneffekt nach StandardOut schreibt.

Haskell oder zumindest der weitverbreitete Glasgow Haskell Compiler (GHC) kommt mit einer integrierten REPL. Haskell hat ein statisches Typensystem, unterstützt die Deklaration eigener Datentypen wie Wochentage und Monate, hat Typinferenz, ist aber nicht objektorientiert. Haskell kennt kein Null. Für optionale Werte kann man stattdessen den Datentyp Maybe verwenden. Maybe a ist dabei definiert als Just a | Nothing, kapselt also einen Wert vom Typ a oder nichts.

Haskell kompiliert nativ und ist speichersicher, hat einen eingebauten Garbage Collector und bietet darüber hinaus gute Unterstützung für Parallelisierung.

Cons

Haskell ist kompliziert. Ich habe mir für die Einarbeitung in Haskell mehr Zeit genommen als für jede andere Sprache bisher, habe bereits eine mittlere zweistellige Anzahl an Funktionen geschrieben und dennoch das Gefühl, immer noch ziemlich am Anfang zu sein, was die Beherrschung der Sprache angeht. Haskell bedient sich sehr stark aus der Mathematik, insbesondere an der Kategorientheorie und dem Lambda-Kalkül. Wer Haskell wirklich verstehen will, muss auch Konzepte wie Funktoren und Monaden verstehen. Um die Sprache zu meistern, ist ein nicht unerheblicher Zeitaufwand und sehr viel Erfahrung erforderlich.

IO in Haskell ist ebenfalls kompliziert. Meiner Meinung nach glänzt die Sprache vor allem dann, wenn man wirklich reine Funktionen schreiben kann. Für eine simple CRUD-Anwendung oder ein sehr zustandsbehaftetes System spielt Haskell aus meiner Sicht seine Vorteile nicht so aus.

Der Zustand vom GHC bzw. dem Paketierer Cabal, über den Haskell-Bibliotheken aus dem Hackage-Repository installiert, aber leider nicht deinstalliert werden können, hinterlässt zumindest auf meinem Manjaro-Gerät einen durchwachsenen Eindruck. In der kurzen Zeit, in der ich mich mit Drittanbieterbibliotheken in Haskell befasst habe, habe ich schon mehrere Probleme damit erlebt. Probleme mit einer mutmaßlich doppelt, in zwei Versionen, vorliegenden Bibliothek konnte ich nur durch händisches Löschen bestimmter Dateien beheben. Ein paar Wochen später ist wohl durch unbewusste Änderungen eine andere Bibliothek kaputt gegangen, was auch dazu geführt hat, dass sich nicht einmal mehr die REPL starten ließ. Auch hier musste ich bestimmte Dateien händisch löschen, um das Problem zu beheben. Eine Bibliothek für SQLite konnte ich erst mit dem Flag -dynamic erfolgreich installieren, zuvor kam es immer zum Abbruch der Installation, da die Abhängigkeit Data.Array.Byte mutmaßlich beschädigt war. Die Probleme mit dem Tooling kann man nicht dem Sprachstandard Haskell anlasten, wer aber Haskell produktiv einsetzen möchte, sollte meiner Meinung nach vorher genug Erfahrung mit dem Ökosystem gemacht haben.

Haskell ist durchschnittlich populär. Das Haskell Package Repository Hackage umfasst zum Zeitpunkt des Beitrags ca. 16000 Pakete. Dem gegenüber gibt es 450000 Pakete im sehr beliebten Python Package Index PyPI – das ist fast 30 mal so viel. Haskell ist bekannt genug, dass man auch eine Kafka- und eine MongoDB-Bibliothek findet, aber das Risiko, dass es hier wenig Alternativen gibt und Projekte einschlafen und sich zum Risiko für ein geschäftskritisches Haskell-Projekt entwickeln, ist ungleich höher.

Datenblatt

NameHaskell
Webseitehttps://www.haskell.org/
Erscheinungsjahr1990
Aktuellste Version (Stand 23. April 2023)Haskell 2010 aus dem Jahr 2010
Typisierungstatisch
Paradigmenfunktional
ProsCons
sehr gut durchdachte und ausdrucksstarke Syntaxkompliziert
ideal für nicht-Seiteneffekt-behaftete Programmelange Lernkurve
viele interessante Sprachkonstrukte wie Pattern Matching und List Comprehensiondurchwachsenes Tooling
Bedarfsauswertung (lazy evaluation)durchschnittliche Verbreitung, durchschnittlich großes Ökosystem
Typensystem, Typinferenz, kein null
integrierte REPL
natives Kompilat, Speichersicherheit, gute Unterstützung für Parallelisierung

virtuallet

virtuallet ist ein kleines Programm von mir, welches ich in diversen Programmiersprachen implementiert habe. Hier geht es direkt zum Haskell-Code von virtuallet auf GitLab. Hier gibt es weitere Infos zu virtuallet.

- learn you a haskell - umfangreiche Einführung in Haskell