Зачем Haskell и что значит «чистый»
Haskell — это язык, где функция всегда честна: одинаковый вход даёт одинаковый выход, и ничего не происходит у вас за спиной.
Главная идея Haskell простая: программа — это набор выражений, которые вычисляются, а не команд, которые что-то меняют. Никаких «сделай это, потом то». Только «чему равно вот это».
Если вы пришли из Python, JavaScript или C++, вы привыкли к переменным, которые меняются, к циклам, которые крутятся, и к функциям, которые могут вывести что-то на экран в любой момент. Haskell отбирает почти всё это — и взамен даёт удивительную надёжность. Программы на Haskell часто работают правильно сразу после того, как компилятор согласился их собрать.
Почему так? Потому что язык чистый. Это значит, что обычная функция в Haskell не может тайком изменить глобальную переменную, записать файл или напечатать строку. Она умеет только одно: взять аргументы и вернуть результат. Звучит как ограничение — но именно оно убирает огромный класс ошибок.
Что такое чистота на пальцах
Функция называется чистой, если выполняются два условия. Первое: при одних и тех же аргументах она всегда возвращает один и тот же результат. Второе: она не имеет побочных эффектов — не меняет ничего во внешнем мире. Вот функция, которая складывает два числа:
add :: Int -> Int -> Int
add x y = x + y
Сколько бы раз вы ни вызвали add 2 3, вы получите 5. Всегда. Это позволяет рассуждать о коде как о математике: можно смело заменить add 2 3 на 5 в любом месте программы, и смысл не изменится. Это свойство называют ссылочной прозрачностью.
Сравните с типичной «грязной» функцией из других языков: она читает текущее время, пишет в лог, дёргает базу данных. Такую функцию нельзя заменить её результатом — ведь результат каждый раз другой, да ещё и что-то происходит вокруг.
ЧИСТАЯ ФУНКЦИЯ ГРЯЗНАЯ ФУНКЦИЯ
вход --> [ f ] --> выход вход --> [ f ] --> выход
(всё) | |
v v
база экран
В знакомом Python ту же идею чистоты легко показать: чистая функция зависит только от своих аргументов.
# Та же идея на Python: чистая функция
def add(x, y):
return x + y
print(add(2, 3)) # 5
print(add(2, 3)) # 5 — всегда одно и то же
# А вот грязная: результат зависит от внешнего состояния
counter = 0
def impure_next():
global counter
counter += 1 # побочный эффект!
return counter
print(impure_next()) # 1
print(impure_next()) # 2 — тот же вызов, другой ответ
Заметьте: add(2, 3) можно мысленно заменить пятёркой где угодно. А impure_next() — нельзя, потому что он каждый раз другой и ещё и меняет counter. Haskell делает первый стиль единственно возможным для обычных функций.
Почему за это любят на практике
Чистота — это не философская прихоть, а инженерное решение с конкретной отдачей. Когда функция не имеет побочных эффектов, её можно безбоязненно вызывать в любом порядке, кешировать результат, выполнять параллельно на нескольких ядрах и тестировать в полной изоляции — ей не нужны ни база, ни сеть, ни мокать окружение. Именно поэтому код на Haskell так удобно рефакторить: вы переставляете и заменяете выражения, не боясь, что где-то вдалеке сломается невидимая связь через общее изменяемое состояние. Большие промышленные системы — от платёжных движков до компиляторов — выбирают Haskell именно за это спокойствие: компилятор и чистота вместе отсекают целые категории дефектов, которые в других языках ловят только тесты или продакшен. Поначалу строгость раздражает, но очень быстро вы начинаете воспринимать её как страховочный трос, а не как кандалы.
Как это мыслить
Перестаньте думать «что делает программа по шагам». Начните думать «чему равно то, что я хочу получить». В Haskell вы описываете результат через выражения, а порядок вычислений компилятор берёт на себя. Это смена угла зрения: с «инструкций» на «определения».
Стоит подчеркнуть и психологический эффект чистоты: она меняет то, как вы читаете чужой код. В императивном языке, увидев вызов функции, вы вынуждены гадать, не поменяла ли она что-то в глобальном состоянии, не записала ли файл, не отправила ли запрос. В Haskell обычная функция таких вопросов не вызывает вовсе — её сигнатура исчерпывающе описывает, что она делает: берёт аргументы, возвращает результат, точка. Это резко снижает когнитивную нагрузку при чтении больших программ. Вы можете рассуждать о куске кода локально, не держа в голове весь остальной проект, потому что незримых связей через изменяемое состояние просто не существует. Именно эта локальность рассуждений, а не синтаксис, и есть главный практический подарок чистоты.
Частые ошибки
- Искать циклы и присваивания. Их почти нет. Повторение — это рекурсия или функции высшего порядка, о которых будет дальше.
- Думать, что чистота мешает делать ввод-вывод. Делать его можно, просто эффекты честно выражаются в типах через
IO— об этом отдельный раздел. - Бояться слова «функциональный». Вы уже писали чистые функции в любом языке. Haskell просто делает их нормой.
Best practices
- Старайтесь писать как можно больше чистых функций — их легко тестировать и невозможно «сломать издалека».
- Эффекты (печать, файлы, сеть) выносите на края программы, а ядро держите чистым.
- Доверяйте компилятору: если он принял программу, очень многое уже гарантированно корректно.
Итог. Haskell — чистый функциональный язык: обычные функции честны и предсказуемы, программа — это выражения, а не команды. Эта строгость кажется ограничением, но именно она даёт ту самую надёжность, ради которой язык и любят.