Haskell — итоги
Кратко — что стоит унести из раздела "Haskell". Если пункт кажется туманным — откройте указанную главу или оглавление.
FAQ — Часто задаваемые вопросы
Типичные сбои и ситуации, с которыми сталкиваются новички после раздела. Здесь — что делать и где копать в главах.
Вопрос. GHC выдаёт "Occurs check: cannot construct infinite type" — что я сделал не так?
Ответ. Функция пытается вернуть структуру, содержащую саму себя без должного типа (например, f x = x : f x). Явно задайте тип списка, используйте fix осознанно или перепишите на конечную структуру. Подробнее здесь — типы, функции.
Вопрос. Программа "течёт" по памяти: take 10 от бесконечного списка всё равно съедает гигабайты.
Ответ. Ленивость держит thunk-цепочку, пока значение не форсируется. Используйте строгие fold (foldl', seq), BangPatterns или streaming-библиотеки вместо [1..] + map. Подробнее здесь — архитектура выполнения, основы FP.
Вопрос. Компилятор: "Couldn't match expected type IO a with actual type a".
Ответ. Эффект ввода-вывода живёт в монаде IO; чистая функция не может вызвать getLine внутри тела. Вынесите IO в main или границу API, ядро оставьте чистым. Подробнее здесь — основы FP, приложения.
Вопрос. Монады кажутся "магией" — без них нельзя?
Ответ. Монада — способ компоновать вычисления с контекстом (Maybe, IO, []). На старте достаточно do-нотации и понимания, что >>= связывает шаги; углубляться в законы — после типов. Подробнее здесь — управляющие конструкции.
Вопрос. head [] или tail [] — программа падает в рантайме без красивой ошибки.
Ответ. Это частичные функции. Замените на pattern matching (case xs of [] -> ...; (h:t) -> ...) или Data.List.NonEmpty. Подробнее здесь — типы и ADT, приложения.
Вопрос. "Non-exhaustive patterns" — match не покрывает все случаи.
Ответ. Компилятор предупреждает о неполном разборе ADT. Добавьте ветку для каждого конструктора или финальный _ с явной ошибкой (error "impossible"). Включите -Wall. Подробнее здесь — типы, управление.
Вопрос. "Ambiguous type variable" — GHC не выводит тип без подсказки.
Ответ. Нужна явная сигнатура или конкретизация через использование (например, show требует Show a). Добавьте :: Type к функции или включите расширения с явным TypeApplications. Подробнее здесь — типы.
Вопрос. Cabal или Stack — что ставить новичку и почему проекты "не собираются"?
Ответ. Оба нормальны: Stack фиксирует снимок GHC; Cabal гибче. Ошибка "cannot satisfy" — конфликт версий в build-depends; читайте полный log, обновите resolver/plan. Подробнее здесь — первая программа, приложения.
Вопрос. Could not find module 'Data.Aeson' — пакет вроде установлен.
Ответ. Модуль должен быть в .cabal / package.yaml и собран тем же cabal/stack, которым вы запускаете ghci. В GHCi загружайте проект через cabal repl, а не голый :l File.hs. Подробнее здесь — приложения.
Вопрос. Путаю String, Text и ByteString — какой тип для JSON и файлов?
Ответ. String — список Char (медленно); для текста и API берите Text; для сырых байтов и сети — ByteString. aeson работает с Text/Value. Подробнее здесь — типы, приложения.
Вопрос. foldl (+) 0 [1..1000000] зависает или OOM, а foldl' работает.
Ответ. Ленивый foldl накапливает невычисленные thunk'и. Для свёрток с числами используйте строгие варианты (foldl', foldr'). Подробнее здесь — функции, архитектура.
Вопрос. do-нотация понятна, а >>= и =<< — нет; как читать цепочку?
Ответ. m >>= f — "взять результат из m и передать в f"; do просто разворачивает это в строки. Нарисуйте desugaring на бумаге для короткого примера с Maybe. Подробнее здесь — управление.
Вопрос. Когда Maybe, когда Either — оба про "может не быть значения".
Ответ. Maybe — факт отсутствия без причины; Either e a — когда нужно сообщить ошибку (Left "div by zero"). В API и CLI предпочитайте Either. Подробнее здесь — типы, приложения.
Вопрос. Обновление поля в record: "Couldn't match type" при user { name = "Ann" }.
Ответ. Синтаксис обновления работает только для того же типа record; поле должно существовать. Для вложенных структур используйте лenses (позже) или явную сборку нового значения. Подробнее здесь — типы.
Вопрос. GHCi :reload не подхватывает изменения в другом модуле.
Ответ. Запускайте cabal repl / stack ghci из корня проекта; после правок — :reload или пересборка. Отдельные файлы без .cabal не видят зависимости автоматически. Подробнее здесь — первая программа.
Вопрос. readFile + length на большом файле — память растёт до размера файла.
Ответ. readFile в lazy IO читает по мере необходимости, но удерживает handle до полного потребления. Для больших данных — streaming (conduit, pipes) или ByteString line-by-line. Подробнее здесь — приложения, архитектура.
Вопрос. Включил OverloadedStrings — теперь "hello" конфликтует с [Char].
Ответ. Литерал строки становится полиморфным (IsString). Явно укажите тип ("hi" :: String) или используйте Text.pack. Подробнее здесь — типы.
Вопрос. JSON decode падает — как отладить без error?
Ответ. Используйте eitherDecode вместо decode, логируйте Left err. Проверьте Generic, имена полей и fieldLabelModifier для camelCase/snake_case. Подробнее здесь — приложения.
Вопрос. f . g . h — в каком порядке идут вызовы? Путаю с pipe в Elixir.
Ответ. Композиция читается справа налево: (f . g) x = f (g x). Для "слева направо" есть & (оператор $> в некоторых преамбулах) или явные скобки. Подробнее здесь — функции и композиция.
Вопрос. Рекурсия без tail optimization — stack overflow на большом n.
Ответ. GHC оптимизирует хвостовую рекурсию не всегда; используйте foldr/foldl', явный accumulator или Control.Monad.ST. Для глубокой рекурсии проверяйте Core (-ddump-simpl). Подробнее здесь — функции, архитектура.
Вопрос. "Haskell нигде не работает" — правда ли, что язык только для академии?
Ответ. Нишевый, но живой: финтех, компиляторы, инфраструктура (Pandoc, xmonad, части блокчейна). Идеи Haskell в Rust, Scala, Swift. Подробнее здесь — история, приложения.
Вопрос. Написал "чистую" функцию, а она печатает — где спрятался IO?
Ответ. Скорее всего вызов Debug.Trace.trace или unsafePerformIO в библиотеке. Ищите trace/putStrLn в transitive deps; для отладки используйте trace осознанно и убирайте перед коммитом. Подробнее здесь — основы FP.
Вопрос. "Orphan instance" — нельзя добавить Show для чужого типа.
Ответ. Instance должны быть в модуле типа или класса (orphan-st rule). Оберните тип в newtype или используйте newtype-обёртку + deriving. Подробнее здесь — типы.
Вопрос. Императивный опыт мешает: хочется менять переменную в цикле.
Ответ. Состояние выражайте через рекурсию, fold, map или монаду State (позже). Начните с первой программы и маленьких чистых функций из приложений. Подробнее здесь — основы FP.
Вопрос. Тип [Char] в ошибке, а я передавал Text — как согласовать?
Ответ. Явно конвертируйте T.unpack / T.pack или приведите сигнатуру API к одному типу строк. Смешение String и Text в одном проекте — частый источник ошибок. Подробнее здесь — типы.
Вопрос. С чего начать pet-проект, если теория "в голове", а руки не помнят синтаксис?
Ответ. Маршрут: первая программа → приложения (TODO на JSON) → вынести чистую логику из main. Один маленький модуль за вечер лучше, чем сразу монадные трансформеры. Подробнее здесь — оглавление.
Вопрос. Haskell с нуля — как начать изучать функциональное программирование?
Ответ. Установите GHC + Stack/Cabal, пройдите типы, функции без переменных, pattern matching. Маршрут — первая программа → основы FP. Подробнее здесь — оглавление.
Вопрос. Что такое монады в Haskell простыми словами?
Ответ. Монада — способ связать шаги вычисления с контекстом (ошибка, IO, список). IO — для консоли и файлов; Maybe — для отсутствующих значений. Подробнее здесь — основы FP, управление.
Вопрос. GHC vs GHCi — в чём разница?
Ответ. GHC компилирует .hs в бинарник; GHCi — интерактивная REPL для экспериментов и :reload. Оба входят в Glasgow Haskell Compiler. Подробнее здесь — первая программа.
Вопрос. Stack или Cabal для Haskell проекта — что выбрать?
Ответ. Stack — фиксированный LTS snapshot, проще новичку; Cabal — гибче для библиотек и Hackage. Оба валидны; главное — один tool на проект. Подробнее здесь — приложения.
Вопрос. Haskell lazy evaluation — что это и зачем?
Ответ. Выражения считаются только когда нужны; бесконечные списки возможны, но возможны space leak. Подробнее здесь — архитектура выполнения, основы FP.
Вопрос. Maybe vs Either Haskell — когда что использовать?
Ответ. Maybe a — значение есть/нет; Either e a — плюс причина ошибки в Left. Для API и CLI чаще Either String a. Подробнее здесь — типы.
Вопрос. Как установить Haskell на Windows 11 / macOS / Linux?
Ответ. Через GHCup (ghcup install ghc, ghcup install stack); проверка ghc --version. IDE — VS Code + HLS. Подробнее здесь — первая программа.
Вопрос. Haskell IO do notation пример — как читать файл?
Ответ. В main: contents <- readFile "f.txt" внутри do; чистую обработку — отдельной функцией process :: String -> String. Подробнее здесь — приложения, основы FP.
Вопрос. Pattern matching Haskell tutorial — как разобрать список?
Ответ. case xs of [] -> ...; (x:xs) -> ... или уравнения f [] = ...; f (h:t) = .... Неполный match — runtime error. Подробнее здесь — типы, управление.
Вопрос. foldr vs foldl Haskell — какой fold выбрать?
Ответ. Для больших числовых свёрток — foldl' (строгий); foldr — когда работаете с ленивым хвостом или функциями. Подробнее здесь — функции.
Вопрос. Haskell каррирование функций — что значит map (+1)?
Ответ. Все функции по умолчанию каррированы: (+) 1 — функция, ждущая второй аргумент. Это основа partial application. Подробнее здесь — функции и композиция.
Вопрос. Algebraic data types Haskell example — как объявить свой тип?
Ответ. data Color = Red | Green | Blue или record data User = User { name :: String }. Потом deriving (Show, Eq). Подробнее здесь — типы.
Вопрос. Learn You a Haskell vs официальная документация — с чего читать по-русски?
Ответ. LYAH — мягкий вход на англ.; в разделе — структурированный маршрут на русском от первая программа. Wiki Haskell.ru — дополнение. Подробнее здесь — оглавление, история.
Вопрос. Haskell в production — где реально применяют?
Ответ. Финтех, компиляторы, Pandoc, xmonad, формальная верификация. Нишевый, но не "мертвый". Подробнее здесь — приложения, история.
Вопрос. String vs Text vs ByteString Haskell — что использовать?
Ответ. Text — UTF-8 текст в приложениях; ByteString — байты/сеть; String — [Char], медленно, legacy. Подробнее здесь — типы.
Вопрос. Как парсить JSON в Haskell (aeson)?
Ответ. DeriveGeneric + FromJSON/ToJSON, eitherDecode для безопасного разбора. Пример трекера — в приложениях.
Вопрос. Haskell vs OCaml vs F# — чем отличается Haskell?
Ответ. Haskell — ленивый, чистый по умолчанию; OCaml/F# — strict, OCaml/F# ближе к ML с мутабельностью. Идеи типов общие. Подробнее здесь — основы FP, история.
Вопрос. Чистая функция Haskell — что значит "pure"?
Ответ. Одинаковые аргументы → одинаковый результат, без побочных эффектов (консоль, сеть, мутация). Эффекты — в IO на краю. Подробнее здесь — основы FP.
Вопрос. Type inference Haskell — зачем писать типы вручную?
Ответ. GHC выводит много сам; явные сигнатуры — документация, ошибки на границе модулей, orphan/instance контроль. Подробнее здесь — типы.
Вопрос. Как написать Hello World на Haskell?
Ответ. main = putStrLn "Hello, World!" в Main.hs, запуск ghc Main.hs && ./Main. Подробнее здесь — первая программа.
Вопрос. Haskell list comprehension example — альтернатива циклу?
Ответ. [ x*2 | x <- [1..10], even x ] — фильтр и map в одной нотации; под капотом — list monad. Подробнее здесь — управление, функции.
Вопрос. Hackage Haskell packages — как найти библиотеку?
Ответ. Поиск на hackage.haskell.org, добавление в .cabal build-depends, cabal build. Stackage snapshot в Stack фиксирует совместимые версии. Подробнее здесь — приложения.
Вопрос. IO monad не монад — мем или правда?
Ответ. Фраза про то, что IO описывает действия, а не "грязь"; формально IO — монада с (>>=). Смысл — держать IO на границе. Подробнее здесь — основы FP, архитектура.
Что запомнить
Haskell — чисто функциональный язык со статической типизацией, ленивыми вычислениями и явным разделением чистого кода и эффектов (IO, монады). Стандарт де‑факто — GHC; экосистема — Cabal/Stack, Hackage, HLS.
Основные особенности:
- Чистые функции и неизменяемые данные — ядро программы;
- Система типов с выводом (Хиндли — Милнер) и ADT (
Maybe,Either, record); - Ленивость — экономия работы, но риск space leak без строгих fold;
- Монады — структурированные эффекты;
IOтолько на границе; - Композиция —
map,., fold вместо циклов и мутаций.
Области применения: финтех и риск, компиляторы и формальная верификация, инструменты (Pandoc, xmonad), EDA/FPGA (Clash), криптография (Cryptol).
Три правила эффективной работы:
- Держите бизнес-логику чистой — тестируемой без моков IO;
- Избегайте частичных функций (
head, голыйread) в продакшен-пути; - Явные сигнатуры типов у публичного API — документация и защита от регрессий.
Куда идти дальше
| Тема | Раздел |
|---|---|
| Scala (JVM, FP+OO) | Scala — о разделе |
| Elixir (BEAM, процессы) | Elixir — о разделе |
| Архитектура выполнения | Архитектура выполнения |
См. также
Другие статьи этого же раздела в боковом меню (как на странице "О разделе"). История Haskell - формирование стандарта функционального программирования и его влияние на теорию и практику. Простые приложения на Haskell — чистые функции, IO на краях, файлы и JSON. Основы функционального программирования на Haskell - чистые функции, неизменяемость и выразительная типовая система. Архитектура выполнения Haskell-программ - ленивые вычисления, чистые функции и модель вычислительного графа. Типизация, набор правил определения типа данных значений языка. Управляющие конструкции и операторы Haskell - логические выражения, сопоставление с образцом и функциональный контроль потока. Функции и типизация в Haskell - строгая статическая модель, вывод типов и композиция чистых функций. Гайд по установке и настройке с написанием первой программы и её запуском.История языка Haskell
Простые приложения на Haskell
Основы функционального программирования на Haskell
Архитектура выполнения Haskell-программ
Типы данных и система типов в Haskell
Управляющие конструкции и операторы Haskell
Функции, каррирование и композиция
Первая программа на Haskell