Перейти к основному содержимому

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).

Три правила эффективной работы:

  1. Держите бизнес-логику чистой — тестируемой без моков IO;
  2. Избегайте частичных функций (head, голый read) в продакшен-пути;
  3. Явные сигнатуры типов у публичного API — документация и защита от регрессий.

Куда идти дальше

ТемаРаздел
Scala (JVM, FP+OO)Scala — о разделе
Elixir (BEAM, процессы)Elixir — о разделе
Архитектура выполненияАрхитектура выполнения

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").