Раздел 21 · Haskell

Выражения, типы и функции

junior~35 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Выражения, типы и функции

В Haskell нет инструкций, есть только выражения. В этом уроке: базовые типы (Int, Integer, Double, Char, Bool), кортежи, сигнатуры функций, каррирование уже не в теории, а в коде, секции операторов, let и where. И главный фокус языка: компилятор выводит типы сам, а сигнатуры ты всё равно пишешь, и не зря.

Идея

В JS ты привык, что код делится на два сорта. Часть кода это инструкции: у if (x) { ... } или for (...) нет значения, их нельзя присвоить переменной или передать в функцию. Часть кода это выражения: x > 0 ? 'плюс' : 'минус' имеет значение, и его можно вставить куда угодно, где значение ожидается.

Haskell отрезает первый сорт целиком. Инструкций нет. Любой кусок программы это выражение, у любого выражения есть значение и тип. Программа целиком тоже одно большое выражение, а её запуск это вычисление, та самая редукция из урока про лямбда-исчисление.

Посмотри, во что это превращает знакомый if:

label :: Int -> String
label n = if n >= 0 then "плюс" else "минус"

Здесь if не разветвление потока управления, а выражение, ближайший родственник тернарника. Отсюда два следствия. Первое: else обязателен всегда, выражение должно иметь значение при любом исходе, ветку нельзя пропустить. Второе: результат if можно передавать дальше, складывать со строками, возвращать. Никакого специального места в синтаксисе у него нет.

Вторая половина утверждения “у каждого выражения есть тип” тянет за собой весь остальной урок. Что такое тип, ты формулировал ещё в уроке про системы типов: множество допустимых значений плюс операции над ними. В Haskell эта связь тотальна: компилятор знает тип каждого подвыражения в программе, и весь урок ты будешь этим пользоваться через :t. Начнём с инвентаря: какие типы выдаются из коробки и как из них собирать функции.

Типы из коробки

Лаборатория

Заведи файл lab02.hs и держи рядом открытый ghci. Цикл работы на весь урок: пишешь функцию в файле, грузишь :l lab02.hs, играешь с ней, правишь, перезагружаешь :r. Всё, что в уроке показано как сессия ghci, набирай руками, это не декорация.

Числа: Int, Integer, Double

ghci> 2 + 3 * 4
14
ghci> maxBound :: Int
9223372036854775807
ghci> (maxBound :: Int) + 1
-9223372036854775808

Int это целое в машинное слово, на твоей машине 64 бита. Переполняется молча, как в Java или Go: прибавили единицу к максимуму, получили минимум. Рядом живёт Integer, целое произвольной точности:

ghci> 2 ^ 100 :: Integer
1267650600228229401496703205376
ghci> product [1..25] :: Integer
15511210043330985984000000

Integer не переполняется никогда, расплата: медленнее и тяжелее по памяти. Рабочее правило: Int для счётчиков, индексов и размеров, где диапазон заведомо влезает; Integer для математики, где числа имеют право расти.

Кстати, набери :t 5 и увидишь странное: 5 :: Num a => a. Числовой литерал в Haskell полиморфен, пятёрка готова стать и Int, и Integer, и Double, смотря куда её поставят. Пока читай Num a => a как “любой числовой тип”, расшифровка этой записи ждёт в уроке 07 про типоклассы.

Double это привычный тебе по JS число с плавающей точкой, со всеми родными оговорками:

ghci> 0.1 + 0.2
0.30000000000000004

Деление расщеплено по типам: / для дробных, div и mod для целых:

ghci> 7 / 2
3.5
ghci> 7 `div` 2
3
ghci> 7 `mod` 2
1

Обрати внимание на обратные кавычки: любую функцию двух аргументов можно поставить между ними в инфиксной записи. div 7 2 и 7 `div` 2 это одно и то же, второе читается ближе к человеческому “семь делить на два”.

И одна ловушка на будущее: отрицательные числа в аргументах требуют скобок. label -3 компилятор прочитает как “из label вычесть три”, пиши label (-3).

Char, Bool и сравнения

Одиночный символ живёт в одиночных кавычках: 'ё' :: Char. Двойные кавычки дают строку, и про неё в этом уроке намеренно ни слова: в Haskell строка это список символов, и весь разговор о ней уедет в урок 03 вместе со списками.

Bool это True и False, обязательно с большой буквы (это конструкторы данных, в уроке 06 станет ясно, почему это важно). Логика: &&, ||, not. Сравнения: == и /=. Неравенство пишется через слеш, это перечёркнутое математическое “равно”, а не восклицательный знак:

ghci> 3 /= 4
True
ghci> not (1 > 2) && ('a' < 'b')
True

Кортежи

Когда надо пронести пару значений разных типов как одно целое, есть кортеж:

release :: (Int, String)
release = (2026, "июнь")

Тип кортежа записывает и длину, и тип каждой позиции: (Int, String) и (String, Int) это разные типы, и (Int, Int, Int) тоже третий, отдельный. Для пар есть готовые fst и snd:

ghci> fst release
2026
ghci> snd release
"июнь"

Кортеж это инструмент “вернуть два значения сразу” и ничего больше. Как только полей становится три и хочется их назвать, это заявка на запись (record), доберёмся в уроке 06.

премиум-урок

Продолжение в премиуме: 1 200 ₽ в месяц или 10 000 ₽ в год. Вместе с ним серверные проверки задач и школьные токены AI-коуча.

Оплата пока не подключена: премиум и курсы открываются промокодом.