Это все еще преимущество, даже если вы пишете сигнатуры типов, потому что компилятор будет отлавливать ошибки типов в ваших функциях. Я обычно тоже пишу сигнатуры типов, но опускаю их в таких местах, как where
или let
, где вы фактически определяете новые символы, но не чувствуете необходимость указывать сигнатуру типа.
Глупый пример со странным способом вычисления квадратов чисел:
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
и sums
- это функции, которым потребуется подпись типа, если компилятор не выведет их автоматически.
Кроме того, если вы используете универсальные функции, как вы обычно делаете, вывод типов - это то, что гарантирует, что вы действительно комбинируете все эти универсальные функции вместе корректным способом. Если вы в вышеприведенном примере скажете
squares :: [a]
squares = ...
Компилятор может сделать вывод, что это недопустимо, поскольку для одной из используемых функций (функция odd
из стандартной библиотеки) необходимо a
в классе типов Integral
. На других языках вы обычно узнаете это только позже.
Если вы напишите это как шаблон в C ++, вы получите ошибку компилятора при использовании функции для нецелого типа, но не при определении шаблона. Это может быть довольно запутанным, потому что не сразу понятно, где вы ошиблись, и вам, возможно, придется просмотреть длинную цепочку сообщений об ошибках, чтобы найти реальный источник проблемы. И в чем-то вроде Python вы получаете ошибку во время выполнения в какой-то неожиданный момент, потому что у чего-то не было ожидаемых функций-членов. А в еще более слабо типизированных языках вы можете не получить никаких ошибок, а только неожиданные результаты.
В Haskell компилятор может гарантировать, что функция может быть вызвана со всеми типами, указанными в ее сигнатуре, даже если это универсальная функция, которая действительна для всех типов, которые выполняют некоторые ограничения (классы типов). Это облегчает программирование универсальным способом и использование универсальных библиотек, что намного сложнее понять в других языках. Даже если вы укажете сигнатуру универсального типа, в компиляторе все еще происходит много выводов типов, чтобы выяснить, какой конкретный тип используется в каждом вызове и соответствует ли этот тип всем требованиям функции.