Система типов и сигнатуры
Тип в Haskell — это обещание, которое компилятор проверяет за вас. Если типы сходятся, целый класс ошибок исчезает ещё до запуска.
Сигнатура length :: [a] -> Int рассказывает почти всё: берём список чего угодно, возвращаем целое. Часто по типу видно, что функция вообще может делать.
Haskell — язык со статической типизацией: тип каждого выражения известен на этапе компиляции. Это значит, что нельзя случайно сложить число со строкой или передать список туда, где ждут одно значение — компилятор откажется собирать программу.
Сигнатура типа пишется через двойное двоеточие ::, которое читается как «имеет тип»:
name :: String
name = "Ада"
age :: Int
age = 36
greet :: String -> String
greet who = "Привет, " ++ who
Как читать стрелки
В сигнатуре функции последняя часть — это результат, всё до неё — аргументы:
greet :: String -> String
| |
аргумент результат
add :: Int -> Int -> Int
| | |
арг1 арг2 результат
Базовые типы, которые встретятся сразу: Int и Integer (целые, второй — неограниченный), Double (дробные), Bool (True/False), Char (символ), String (строка, она же [Char]).
Типы помогают, а не мешают
Можно не писать сигнатуры — GHC выведет типы сам. Но писать их полезно: это и документация, и проверка ваших намерений. Если вы думали, что функция возвращает Int, а написали тело, дающее String, компилятор тут же укажет на расхождение.
-- если перепутать тип, GHC не пропустит:
bad :: Int
bad = "это не число" -- ОШИБКА компиляции
В Python типы динамические, но аннотации (type hints) дают похожую идею «обещания» — правда, без жёсткой проверки во время выполнения:
# Та же идея на Python: аннотации типов как обещание
def greet(who: str) -> str:
return "Привет, " + who
def add(x: int, y: int) -> int:
return x + y
print(greet("Ада")) # Привет, Ада
print(add(2, 3)) # 5
# В Python это лишь подсказка: проверяют её внешние инструменты,
# а Haskell проверяет такие обещания строго при компиляции.
Типы как мышление, а не как формальность
Опытные хаскелисты часто пишут сигнатуру раньше тела — и не из дисциплины, а потому что тип задаёт каркас решения. Сформулировав «функция берёт список и возвращает число», вы уже сузили пространство реализаций и можете двигаться уверенно. Сообщения компилятора об ошибках типов поначалу кажутся стеной текста, но в них почти всегда зашита точная диагностика: где ожидался один тип, а пришёл другой. Научившись читать их спокойно, вы превратите компилятор в напарника, который ловит опечатки и логические нестыковки до того, как они доберутся до запуска. Ещё одна привычка, экономящая часы, — спрашивать :t у незнакомого выражения: тип нередко объясняет поведение лучше любого описания. В Haskell типы — это не бюрократия, а инструмент мышления, который ведёт вас к корректному коду.
Как это мыслить
Читайте сигнатуру раньше тела. Часто тип задаёт настолько узкие рамки, что реализация почти очевидна. «Тип сначала, код потом» — мощная привычка: вы формулируете, что хотите получить, и компилятор ведёт вас к корректной реализации.
Со временем вы оцените ещё одно следствие сильной типизации — так называемое «программирование, управляемое типами». Зная сигнатуру, можно оставить тело функции незаполненным (поставив «дырку» _), и компилятор подскажет, значение какого типа здесь ожидается. Двигаясь от типа к реализации маленькими шагами и спрашивая у компилятора, что подходит в каждое место, вы нередко собираете корректную функцию почти механически. Этот диалог с системой типов — мощная техника, недоступная в динамических языках. Она превращает написание кода из угадывания в направленный поиск, где компилятор на каждом шаге сужает варианты и отсекает заведомо неверные. Чем сложнее задача, тем заметнее, как типы ведут вас к решению, а не просто проверяют его постфактум.
Частые ошибки
- Путать
::и:. Двойное двоеточие — «имеет тип», одиночное — оператор добавления в список. - Смешивать
IntиDouble. Haskell не приводит их молча; используйтеfromIntegralпри необходимости. - Игнорировать сообщения о типах. Они длинные, но почти всегда точно указывают на проблему.
Best practices
- Пишите сигнатуры у всех функций верхнего уровня.
- Если запутались — спросите
:t выражениев GHCi. - Воспринимайте ошибку типизации как подсказку, а не как преграду.
Итог. Статическая типизация ловит ошибки до запуска, сигнатура через :: описывает аргументы и результат, а типы служат и проверкой, и документацией. Привыкните читать сигнатуры — и Haskell станет понятнее.