Haskell ist eine streng getypte Sprache. Eine getypte Sprache zeichnet sich dadurch aus, dass verschiedene Arten von Werten (Typen) unterschieden werden. Dies ist in den meisten gebräuchlichen Programmiersprachen der Fall. Auch dass jedem (wohlgeformten) Ausdruck ein Typ zugeordnet werden kann, gilt für viele Programmiersprachen. Das entscheidende Merkmal einer streng getypten Sprache ist darüber hinaus, dass der Typ jedes Ausdrucks zur Übersetzungszeit aus den Typen der einzelnen Komponenten des Ausdrucks geschlossen werden kann (Typinferenz). Die Bestimmung und Überprüfung von Typen erfolgt komplett statisch.
Im Umkehrschluss ermöglicht diese strenge Typisierung, Ausdrücke, denen im Rahmen der Inferenz kein Typ zugewiesen werden kann, bereits zur Übersetzungszeit als fehlerhaft abzulehnen. Dazu wird im Anschluss an die Prüfung auf korrekte Syntax stets auch eine Typüberprüfung durchgeführt, bevor mit der Auswertung begonnen wird. Damit werden einige sehr verbreitete Fehlerquellen wie Tippfehler oder fehlerhafte Definitionen schon frühzeitig ausgeschlossen. Darüber hinaus ist der generierte Code effizienter, da er frei ist von jeglichen Typüberprüfungen.
Der Inferenzmechanismus von Haskell ist so ausgeprägt, dass der Programmierer Typensignaturen (bis auf wenige Ausnahmen) überhaupt nicht explizit zu deklarieren braucht. Die Typen werden aus dem Kontext ermittelt. Trotzdem gibt es Gründe, die für explizite Typendeklarationen sprechen: zum einen wird dadurch die Fehlersuche erleichtert, zum anderen erleichtern sie Dritten das Verständnis des Quellcodes.
Wie die meisten Programmiersprachen sind auch in Haskell bereits einige elementare Datentypen vordefiniert:
Int
und Integer
Float
und Double
Char
Bool
String
) ist in Haskell nichts anderes als eine Liste von Char.
In einer streng getypten Sprache wie Haskell kann jedem wohlgeformten Ausdruck bereits zur Übersetzungszeit ein Typ zugeordnet werden. Auch Funktionen werden Typen zugeordnet. Einigen Funktionen müssten jedoch je nach Ausdruck, in dem sie verwendet werden, unterschiedliche Typen zugeordnet werden.
So hängt zum Beipiel der Resultattyp der Funktion head
, die das erste Element einer nicht-leeren Liste zurückgibt, vom Typ dieses Listenelementes ab.
Auch im Falle der Funktionskomposition (.)
kann die Typenzuordnung variieren.
Beipielfunktionen für die Komposition seien:
square :: Integer -> Integer
sqrt :: Integer -> Float
Im Falle der Komposition (square . square)
ergibt sich als Typsignatur für die Funktionskomposition:
(.) :: (Integer->Integer) -> (Integer->Integer) -> (Integer->Integer)
(sqrt . square)
ergibt sich als Typsignatur für die Funktionskomposition:(.) :: (Integer->Float) -> (Integer->Integer) -> (Integer->Float)
Um der Funktionskomposition trotzdem nur eine einzige Typsignatur zuzuweisen, führt man Typvariablen ein. Diese können dann je nach Anwendungsfall für verschiedene Typen stehen. Deshalb wird eine solche Typsignatur auch als polymorph bezeichnet.
Die Typsignatur der Funktionskomposition unter Verwendung der Typvariablen a, b, c:
(.) :: (b -> c) -> (a -> b) -> (a -> c)
Der Vollständigkeit halber hier noch die Typensignatur der erwähnten Funktion head
unter verwendung einer Typvariablen a:
head :: [a] -> a
Auch für die Multiplikation (*)
existieren mehrere sinnvolle Typsignaturen:
(*) :: Integer -> Integer -> Integer
(*) :: Float -> Float -> Float
Eine Signatur mit Typvariablen wäre jedoch zu allgemein und würde zu Problemen bei der Überprüfung der Typen zur Übersetzungszeit führen. Denn die Multiplikation zweier Ausdrücke vom Typ Char
oder Bool
ergibt zwar keinen Sinn, wäre bei einer Signatur mit Typvariablen aber zulässig. Stattdessen werden Typklassen verwendet, die ähnliche Typen zusammenfassen. Im Fall der Multiplikation kann auf die Typklasse Num
, also die Klasse aller Zahlen, zurückgegriffen werden:
(*) :: Num a => a -> a -> a
Diese Typsignatur steht also für den Typ a -> a -> a, wobei a eine Instanz der Typklasse Num
sein muss. Sowohl Integer
als auch Float
sind Instanzen der Typklasse Num
.
Auch numerischen Konstanten wird die Typklasse Num
zugeordnet, da zum Beipiel die Zahl 5 eine ganze Zahl genauso repräsentieren kann wie eine Fliesskommazahl. Der der Zahl 5 zugeordnete Typ wäre Num a => a
.
Typklassen können nach vielfältigen Kriterien gebildet werden und müssen nicht überschneidungsfrei sein. So kann ein Typ zum Beipiel Instanz der Typklasse aller Zahlen (Num) sein und gleichzeitig Instanz der Klasse aller Typen, für die ein Gleichheitstest definiert ist (Eq).
Der vom Compiler zur Bestimmung des Typs von Ausdrücken genutzte Inferenzmechanismus kann auch vom Programmierer in der Shell genutzt werden. Dazu wird dem Ausdruck, von dem der Typ ermittelt werden soll, der Befehl :t
oder :type
vorangestellt.
Beispiele:
Prelude> :t 's'
's' :: Char
Prelude> :t 'p' == 'q'
'p' == 'q' :: Bool
Prelude> :t 5
5 :: Num a => a