Система типов и сигнатуры

Тип в 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 станет понятнее.

Проверьте себя
1. Как читается символ :: в сигнатуре?
A«равно»
B«имеет тип»
C«добавить в список»
D«комментарий»
2. Что означает сигнатура add :: Int -> Int -> Int?
AФункция возвращает три числа
BФункция берёт два Int и возвращает Int
CФункция берёт список Int
DЭто объявление переменной типа Int