Es ist immer noch ein Vorteil, auch wenn Sie Typ-Signaturen schreiben, weil der Compiler in Ihren Funktionen Typfehler feststellt. Normalerweise schreibe ich auch Typensignaturen, lasse sie aber an Stellen wie where
oder let
Klauseln weg, in denen Sie zwar neue Symbole definieren, aber keine Typensignatur angeben müssen.
Dumme Beispiel mit einer seltsamen Art und Weise Quadrate von Zahlen berechnen:
squares :: [Int]
squares = sums 0 odds
where
odds = filter odd [1..]
sums s (a:as) = s : sums (s+a) as
square :: Int -> Int
square n = squares !! n
odds
und sums
Funktionen sind, die eine Art Signatur benötigen würde, wenn der Compiler sich nicht automatisch geschlossen werden würde.
Auch wenn Sie generische Funktionen verwenden, wie Sie normalerweise tun, ist Typ Inferenz, was sicherstellt, dass Sie wirklich alle diese generischen Funktionen in einer gültigen Weise kombinieren. Wenn Sie im obigen Beispiel, sagen
squares :: [a]
squares = ...
Der Compiler ableiten kann, dass dies so nicht gültig ist, weil einer der verwendeten Funktionen (die odd
Funktion aus der Standardbibliothek), a
muss in seine die Typklasse Integral
. In anderen Sprachen erkennen Sie dies in der Regel erst zu einem späteren Zeitpunkt.
Wenn Sie dies als Vorlage in C++ schreiben, erhalten Sie einen Compilerfehler, wenn Sie die Funktion für einen nicht-integralen Typ verwenden, aber nicht, wenn Sie die Vorlage definieren. Dies kann ziemlich verwirrend sein, da nicht sofort klar ist, wo Sie einen Fehler gemacht haben, und Sie müssen möglicherweise eine lange Kette von Fehlermeldungen durchsuchen, um die tatsächliche Ursache des Problems zu finden. Und in etwas wie Python erhalten Sie den Fehler zur Laufzeit an einem unerwarteten Punkt, weil etwas nicht die erwarteten Elementfunktionen hatte. Und in noch lockereren Sprachen erhalten Sie möglicherweise keine Fehler, sondern nur unerwartete Ergebnisse.
In Haskell kann der Compiler sicherstellen, dass die Funktion mit allen in der Signatur angegebenen Typen aufgerufen werden kann, auch wenn es sich um eine generische Funktion handelt, die für alle Typen gilt, die bestimmte Einschränkungen erfüllen (auch Klassen genannt). Dies macht es einfach, generisch zu programmieren und generische Bibliotheken zu verwenden, was in anderen Sprachen viel schwieriger ist. Selbst wenn Sie eine generische Typ-Signatur angeben, wird im Compiler immer noch eine Menge von Typ-Inferenzen ausgeführt, um herauszufinden, welcher spezifische Typ bei jedem Aufruf verwendet wird und ob dieser Typ alle Anforderungen der Funktion erfüllt.