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

Cabal и Stack

Разработчику Архитектору

Дальше: простые приложения · монады


Cabal и Stack

Haskell-код редко живёт в одном .hs файле. Cabal (Common Architecture for Building Applications and Libraries) — стандарт описания пакетов и сборки в экосистеме GHC. Stack — инструмент с фиксированными снимками GHC и Stackage для воспроизводимых сборок.

Оба используют реестр Hackage (hackage.haskell.org). Перед этой главой полезно пройти первую программу и монады.

Практикум по шагам — установка GHC, проект Cabal, проект Stack, зависимости, тесты, REPL, HLS и типичные ошибки.

ШагТемаЗачем
1GHCup и выбор инструментаПодготовить среду
2Cabal-проект с нуляСтандартная сборка
3stack newБыстрый старт с LTS
4Зависимостиaeson, text, mtl
5REPL и тестыЦикл разработки
6HLS в редактореПодсказки и типы
7CI и freezeВоспроизводимость
8Миграция и чек-листЗакрепление
МатериалЗачем
Первая программаGHCi, main, hello world
Монадыmtl в зависимостях
Простые приложенияJSON, HTTP после сборки
npm в NodeАналог менеджера пакетов
КонфигурацииМанифесты и lock-файлы
sbt в ScalaСборка на JVM

Навигация по блоку Haskell

Один инструмент на проект

Не смешивайте cabal build и stack build в одном репозитории без явной причины. Выберите Cabal или Stack, опишите выбор в README — так проще CI и онбординг коллег.


Шаг 1 — GHCup и выбор инструмента

GHCup (www.haskell.org/ghcup) устанавливает GHC, Cabal, Stack и HLS на Linux, macOS и Windows.

# Проверка после установки
ghc --version
cabal --version
stack --version

Рекомендуемый GHC для новых проектов — ветка 9.6 или 9.8 (LTS Stack подбирается под них).

Cabal или Stack

КритерийCabalStack
Воспроизводимостьcabal.project + freezeLTS snapshot из коробки
НовичкуГибче, больше настройкиПроще старт
Актуальные пакеты HackageПолный индексПодмножество Stackage
Версия GHCwith-compiler в projectresolver в yaml
CIcabal buildstack build

Stack — быстрый старт, фиксированный LTS, меньше конфликтов версий на первых проектах.

Cabal — гибкость, свежие версии с Hackage, стандарт инструментов GHCup.

ИнструментАналог
Cabalnpm + package.json, Cargo
Stackфиксированный toolchain + lock
Hackagenpm registry, crates.io
Stackageкурируемый набор пакетов

Шаг 2 — Cabal-проект с нуля

Структура каталогов

my-app/
├── my-app.cabal
├── cabal.project
├── app/
│ └── Main.hs
└── src/
└── Lib.hs

Создание проекта

mkdir my-app && cd my-app
cabal init -n --is-executable

Флаг -n — non-interactive; --is-executable — исполняемый пакет с main.

Минимальный Main.hs

-- app/Main.hs
module Main where

import Lib (greet)

main :: IO ()
main = putStrLn (greet "Haskell")
-- src/Lib.hs
module Lib (greet) where

greet :: String -> String
greet name = "Hello, " ++ name

Сборка и запуск

cabal build
cabal run my-app

Разбор:

  • cabal build компилирует все компоненты из .cabal.
  • cabal run my-app собирает при необходимости и запускает executable my-app.
  • Имя executable совпадает со stanza в .cabal.

Фрагмент my-app.cabal

cabal-version: 3.0
name: my-app
version: 0.1.0.0
build-type: Simple

executable my-app
main-is: Main.hs
hs-source-dirs: app
other-modules: Lib
build-depends:
base ^>=4.18.0.0,
my-app
default-language: Haskell2010

library
hs-source-dirs: src
exposed-modules: Lib
build-depends: base ^>=4.18.0.0
ПолеСмысл
build-dependsЗависимости компонента
^>=4.18Совместимость с major 4.18
hs-source-dirsКаталоги с модулями
default-languageHaskell2010 или GHC2021

Библиотека и executable в одном пакете — типичная схема: логика в Lib, точка входа в Main.


Шаг 3 — cabal.project и freeze

Файл cabal.project в корне задаёт параметры всего workspace:

packages: .
with-compiler: ghc-9.6.5
ОпцияНазначение
packages: .Собирать пакет в текущей папке
packages: app libНесколько пакетов в монорепо
with-compilerФиксировать версию GHC

Воспроизводимость

cabal update
cabal build
cabal freeze

Создаётся cabal.project.freeze — точные версии всех зависимостей. Коммитьте freeze в приложения и CI.

# На чистой машине
cabal build --enable-tests
cabal update

Перед добавлением новых пакетов выполните cabal update — обновляется локальный индекс Hackage. Без этого solver может не найти свежие версии.


Шаг 4 — Stack-проект

Создание

stack new my-app
cd my-app
stack build
stack run

Команда stack new генерирует шаблон с package.yaml (hpack) или .cabal, stack.yaml и app/Main.hs.

stack.yaml

resolver: lts-22.28

packages:
- .

extra-deps: []
ПолеСмысл
resolverLTS = GHC + набор версий Stackage
packagesЛокальные пакеты
extra-depsПакеты вне snapshot

Список LTS: stackage.org. Выбирайте resolver с вашей версией GHC.

package.yaml (hpack)

Stack часто использует hpack — YAML, из которого генерируется .cabal:

name: my-app
version: 0.1.0.0

executables:
my-app-exe:
main: Main.hs
source-dirs: app
dependencies:
- base
- my-app

library:
source-dirs: src
exposed-modules:
- Lib

После правки package.yaml:

stack build

Шаг 5 — добавление зависимостей

Cabal (современный способ)

cabal add aeson text mtl
cabal build

Команда обновляет build-depends в .cabal и запускает solver.

Ручное редактирование .cabal

executable my-app
build-depends:
base ^>=4.18.0.0,
aeson >=2.0 && <2.3,
text,
mtl

Stack

В package.yaml или .cabal:

dependencies:
- aeson
- text
- mtl
stack build

Пример — чтение JSON

{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson (decode)
import qualified Data.ByteString.Lazy as BL

data User = User { userName :: String } deriving (Show)

-- instance FromJSON User ... (упрощённо опустим)

main :: IO ()
main = do
content <- BL.readFile "user.json"
print (decode content :: Maybe User)

Полный пример с FromJSON — в простых приложениях.


Шаг 6 — REPL, тесты и документация

ЗадачаCabalStack
REPL с зависимостямиcabal replstack ghci
REPL для libcabal repl lib:my-appstack ghci my-app:lib
Тестыcabal teststack test
Документацияcabal haddockstack haddock
Очисткаcabal cleanstack clean

Test stanza в .cabal

test-suite my-app-test
type: exitcode-stdio-1.0
hs-source-dirs: test
main-is: Spec.hs
build-depends:
my-app,
base,
hspec
default-language: Haskell2010
-- test/Spec.hs
import Test.Hspec
import Lib (greet)

main :: IO ()
main = hspec $ do
describe "greet" $ do
it "adds Hello" $ greet "Ann" `shouldBe` "Hello, Ann"
cabal test
# или
stack test

GHCi без полного проекта

ghci app/Main.hs

Для одного файла без зависимостей — см. первую программу. Как только появляются пакеты с Hackage — только через Cabal или Stack.


Шаг 7 — HLS и редактор

Haskell Language Server (HLS) даёт подсказки типов, переход к определению и диагностику в VS Code / Cursor.

ghcup install hls
ghcup tui

hie.yaml для Stack

cradle:
stack:
component: "my-app:exe:my-app"

hie.yaml для Cabal

cradle:
cabal:
component: "my-app:exe:my-app"

Компонент уточняйте через cabal repl -v0 или документацию HLS.

HLS и версия GHC

HLS привязан к версии GHC проекта. После смены resolver или with-compiler переустановите или переключите HLS через ghcup.


Шаг 8 — CI, форматирование, публикация

Минимальный CI (GitHub Actions, Cabal)

- run: cabal update
- run: cabal build --enable-tests
- run: cabal test

Для Stack:

- run: stack build --test
- run: stack test

Форматирование

ИнструментКоманда
fourmolufourmolu -i src/
ormoluormolu --mode inplace $(find src -name '*.hs')

Добавьте проверку формата в CI — единый стиль в команде.

Публикация на Hackage

cabal sdist
cabal upload dist-newstyle/sdist/my-app-0.1.0.0.tar.gz

Требуется аккаунт на Hackage и корректные license, synopsis, description в .cabal.


Типичные ошибки сборки

СообщениеПричинаДействие
cannot satisfy / Could not resolveКонфликт версийcabal update, ослабить bounds, другой LTS
Module not found: XНет в build-dependscabal add X или правка .cabal
Couldn't match expected typeНеверный GHCСменить resolver / with-compiler
Cyclic module dependenciesИмпорты по кругуВынести общий код в третий модуль
Variable not in scopeНе экспортирован модульДобавить в exposed-modules
Долгая первая сборкаКомпиляция зависимостейНормально; кэш в dist-newstyle / .stack-work
HLS краснит, build окУстаревший hie.yamlПерегенерировать cradle

Полный лог в CI не сокращайте до одной строки — первая причина часто в середине вывода solver.


Миграция между Cabal и Stack

Cabal → Stack

cd existing-cabal-project
stack init
# подобрать resolver с вашим GHC
stack build

stack init анализирует .cabal и предлагает resolver.

Stack → Cabal

  1. Скопировать build-depends из .cabal / package.yaml.
  2. Создать cabal.project с with-compiler.
  3. cabal freeze для lock.
  4. Удалить stack.yaml из репозитория (после проверки CI).

Документируйте выбор в README и в конфигурациях команды.


Практический чек-лист

  1. ghcup установил GHC 9.6+.
  2. stack new или cabal init — hello world собирается и запускается.
  3. Добавили aeson, прочитали JSON — приложения.
  4. stack test / cabal test — один hspec-тест зелёный.
  5. HLS показывает типы в Main.hs.
  6. fourmolu или ormolu в pre-commit или CI.
  7. Freeze / LTS закоммичен для воспроизводимости.

Упражнения

  1. Создайте Cabal-проект greeter с библиотекой и executable; вынесите greet в Lib.
  2. Добавьте зависимость text, замените конкатенацию ++ на Data.Text.pack / unpack в одной функции.
  3. Напишите hspec-тест, который падает, и исправьте код до зелёного cabal test.
  4. Сгенерируйте cabal.project.freeze и соберите проект на второй машине (или в CI).
  5. Создайте тот же проект через stack new и сравните структуру файлов с Cabal.

FAQ

Можно ли использовать и Cabal, и Stack в одном проекте? Технически да, но команда и CI путаются. Обычно выбирают один путь.

Что такое Stackage? Курируемый набор версий пакетов, совместимых с конкретным GHC. Stack скачивает готовые планы сборки.

Нужен ли hpack? Не обязателен. Удобен, когда не хотите править .cabal вручную — Stack генерирует его из package.yaml.

Где хранятся артефакты? Cabal — dist-newstyle/. Stack — .stack-work/. Оба каталога в .gitignore.

Как обновить зависимости? Cabal: cabal update, правка bounds, cabal build. Stack: смена resolver или stack upgrade.

Аналог в других языках? npm — Node 265; mix — Elixir; Cargo — Rust.


Глоссарий

ТерминОпределение
CabalФормат пакета .cabal и CLI сборки
StackCLI с LTS и изолированными сборками
HackageРеестр Haskell-пакетов
StackageСнимок совместимых версий
ResolverИдентификатор LTS/nightly в Stack
StanzaСекция в .cabal (executable, library, test)
SolverПодбор версий зависимостей Cabal
FreezeЗафиксированные версии в lock-файле
hpackГенератор .cabal из YAML
HLSLanguage Server для Haskell

Что дальше

  • Nix + Cabal для hermetic builds.
  • cabal-fmt для форматирования .cabal.
  • Публикация библиотеки на Hackage.
  • Прикладной код — простые приложения.
  • Теория эффектов — монады.
Итог

Cabal и Stack — инфраструктура Haskell. Без них сложно перейти от упражнений в GHCi к поддерживаемым проектам с тестами, зависимостями и CI. Выберите один инструмент, пройдите чек-лист и переходите к простым приложениям.


Шаг 9 — монорепозиторий и несколько пакетов

Когда в одном git-репозитории несколько библиотек:

workspace/
├── cabal.project
├── lib-core/
│ └── lib-core.cabal
├── lib-api/
│ └── lib-api.cabal
└── app-web/
└── app-web.cabal
-- cabal.project
packages:
lib-core
lib-api
app-web

with-compiler: ghc-9.6.5
cabal build all
cabal run app-web
cabal repl lib:lib-core

Разбор:

  • packages перечисляет каталоги с .cabal.
  • app-web в build-depends указывает lib-core, lib-api.
  • cabal build all собирает весь workspace.

Шаг 10 — флаги, benchmarks, санитайзеры

compile-time флаги в .cabal

flag dev
description: Enable development warnings
default: False
manual: True

library
if flag(dev)
ghc-options: -Wall -Wcompat
cabal build -fdev

benchmark stanza

benchmark my-app-bench
type: exitcode-stdio-1.0
main-is: Bench.hs
hs-source-dirs: bench
build-depends: my-app, base, criterion
cabal bench

Stack с GHC options

# stack.yaml
ghc-options:
"$locals": -Wall
"$everything": -O2

Шаг 11 — Docker и воспроизводимая сборка

Минимальный Dockerfile для Stack:

FROM haskell:9.6
WORKDIR /app
COPY stack.yaml package.yaml ./
RUN stack setup
COPY . .
RUN stack build
CMD ["stack", "exec", "my-app-exe"]

Для Cabal с freeze:

COPY cabal.project cabal.project.freeze my-app.cabal ./
RUN cabal update && cabal build

Кэшируйте слой с зависимостями отдельно от исходников — так CI быстрее.


Шаг 12 — hpack и cabal-fmt

hpack генерирует .cabal из YAML — меньше дублирования:

# package.yaml
name: my-app
dependencies:
- base >= 4.18 && < 5

library:
source-dirs: src
exposed-modules:
- Lib

executables:
my-app-exe:
main: Main.hs
source-dirs: app
stack build # hpack вызывается автоматически

cabal-fmt выравнивает .cabal для ревью:

cabal-fmt my-app.cabal -i

Шаг 13 — зависимости из Git

Cabal

source-repository-package
type: git
location: https://github.com/user/lib.git
tag: v1.2.0

Stack extra-deps

extra-deps:
- git: user@github.com:user/lib.git
commit: abc123def

Используйте для форков до публикации на Hackage.


Шаг 14 — отладка solver Cabal

Типичный сценарий cabal build падает с Could not resolve dependencies:

  1. cabal update
  2. cabal build -v3 — полный лог solver
  3. Проверить ^>= и верхние границы в .cabal
  4. Временно ослабить bound проблемного пакета
  5. cabal freeze после успешной сборки
cabal list-bin my-app
# путь к скомпилированному executable

Шаг 15 — сравнение с npm и Cargo

ЗадачаCabalStacknpmCargo
Манифест.cabalpackage.yamlpackage.jsonCargo.toml
Lockcabal.project.freezeчерез snapshotpackage-lock.jsonCargo.lock
Установка depscabal buildstack buildnpm installcargo build
REPLcabal replstack ghcinode REPLнет встроенного
Тестыcabal teststack testnpm testcargo test

Общая теория — конфигурации и данные.


Расширенные упражнения

  1. Добавьте benchmark с criterion на функцию из Lib.
  2. Настройте fourmolu в GitHub Actions вместе с cabal test.
  3. Создайте workspace из lib + exe и подключите lib как зависимость.
  4. Сгенерируйте hie.yaml через stack ide configurations (или документацию HLS).
  5. Опубликуйте учебный пакет на локальный cabal store и подключите как dependency.

Расширенный FAQ

Что в dist-newstyle? Артефакты Cabal v2 — не удаляйте без причины; cabal clean безопаснее.

stack install устарел? Используйте stack install для глобальных утилит или stack build + stack exec.

Как зафиксировать GHC в команде? Cabal: with-compiler в cabal.project. Stack: resolver. Документируйте в README.

Нужен ли Nix? Для hermetic CI — да, опционально. Для учебного проекта достаточно freeze или LTS.

Как подключить mtl после монады? cabal add mtl — см. монады, затем ReaderT в приложении.


Чек-лист перед code review

ПунктПроверка
Сборка с нуляcabal build / stack build на чистой машине
Тестыcabal test зелёный
Форматormolu / fourmolu
HLSНет ложных ошибок в Main.hs
Freeze / LTSЗакоммичен
READMEУказан Cabal или Stack
boundsНет лишних == без причины

Полный walkthrough — проект notes-json

Учебный проект читает JSON с диска — связка Cabal + aeson из простых приложений.

1. Инициализация

stack new notes-json simple
cd notes-json
stack add aeson text bytestring

2. Файл data/note.json

{"title": "Learn Monads", "done": false}

3. Lib.hs — чистый парсинг

{-# LANGUAGE DeriveGeneric #-}
module Lib where

import Data.Aeson (FromJSON, decode)
import qualified Data.ByteString.Lazy as BL
import GHC.Generics

data Note = Note { title :: String, done :: Bool }
deriving (Show, Generic, FromJSON)

loadNote :: FilePath -> Either String Note
loadNote path =
case BL.readFile path >>= decode of
Nothing -> Left "invalid JSON"
Just n -> Right n

4. Main.hs — тонкий IO

module Main where

import Lib
import System.Environment (getArgs)

main :: IO ()
main = do
args <- getArgs
case args of
[path] ->
case loadNote path of
Left e -> print e
Right n -> print n
_ -> putStrLn "usage: notes-json <file>"

5. Запуск

stack build
stack exec notes-json-exe data/note.json

Ошибки парсинга остаются в Either — тот же паттерн, что в монадах.


Ссылки на смежные темы

ТемаМатериал
Первая программа7.md
Монады и Either8.md
npm и lockNode 265
Конфиги3.04 Конфигурации
CI/CDРазработка

В подборках

Статья входит в маршрут Haskell: Первая программаМонадыCabal и StackПростые приложения.


hpack и генерация cabal-файлов

hpack описывает пакет в YAML; hpack генерирует .cabal:

# package.yaml
name: my-app
version: 0.1.0.0
dependencies:
- base >= 4.14 && < 5
- aeson
executables:
my-app-exe:
main: Main.hs
source-dirs: app

Плюсы: меньше дублирования, читаемые зависимости. Минус: нужен шаг генерации в CI (hpack перед cabal build).

ИнструментФайл источникаРезультат
hpackpackage.yaml.cabal
cabal initинтерактив.cabal
stack initшаблонpackage.yaml или .cabal

Мультипакетный репозиторий (Cabal)

Структура monorepo:

repo/
cabal.project
lib-core/
lib-core.cabal
app-web/
app-web.cabal

cabal.project:

packages: lib-core app-web
tests: true

Команда cabal build all собирает граф зависимостей между локальными пакетами. Версии согласуют через общий cabal.project.freeze или plan.json в CI.


Stack snapshot и LTS

Stackage LTS — снимок Hackage, где все пакеты совместимы:

# stack.yaml
resolver: lts-22.28
packages:
- .
extra-deps:
- some-package-1.2.3@sha256:...

extra-deps нужен для пакетов вне snapshot. Зафиксируйте @sha256 для воспроизводимости — аналог lockfile в npm (Node 265).


CI для Haskell-проекта

Типичный pipeline GitHub Actions:

- uses: haskell-actions/setup@v2
with:
ghc-version: '9.6'
cabal-version: '3.10'
- run: cabal update
- run: cabal build all
- run: cabal test all

Для Stack — stack build --test --no-run-tests или stack test. Кэшируйте ~/.cabal/store и dist-newstyle.

ШагCabalStack
Lock depscabal freezestack.yaml + resolver
Testcabal teststack test
HPC coverage--enable-coverage--coverage

Зависимости: best practices

  1. Указывайте верхнюю границу в .cabal (base < 5), иначе завтрашний Hackage сломает сборку.
  2. Минимизируйте transitive deps — каждый пакет увеличивает время сборки CI.
  3. Пинning в приложенияхcabal freeze или Stack snapshot; библиотеки — широкие bounds.
  4. Проверяйте licensescabal-plan license или stack ls dependencies.

GHC options и warnings

ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates

В cabal.project:

package *
ghc-options: -Werror

-Werror в CI ловит предупреждения как ошибки — полезно для команд, но мешает локальной разработке на новом GHC.


Профилирование и оптимизация

cabal build --enable-profiling
cabal exec my-app-exe -- +RTS -p

Файл my-app-exe.prof — cost center report. Stack: stack build --profile, затем +RTS -p.

Флаг RTSНазначение
-ptime profile
-hheap profile
-ssummary stats

Nix и reproducible builds

Nix + haskell.nix или stack2nix дают bit-identical окружение:

# flake.nix (упрощённо)
packages.default = pkgs.haskellPackages.callCabal2nix "my-app" ./. {};

Плюс — полная воспроизводимость; минус — кривая обучения. Для большинства команд достаточно cabal freeze + Docker образ с фиксированным GHC.


Docker для деплоя

Многоэтапная сборка:

FROM haskell:9.6 AS build
WORKDIR /app
COPY . .
RUN cabal update && cabal build --enable-executable-static

FROM debian:bookworm-slim
COPY --from=build /app/dist-newstyle/.../my-app-exe /usr/local/bin/
CMD ["my-app-exe"]

Статическая линковка (-static) упрощает runtime image, но увеличивает размер бинарника.


Отладка cabal/stack ошибок

СообщениеЧастая причинаДействие
Could not resolve dependenciesКонфlicting boundscabal outdated, ослабить/ужесточить bounds
Module not foundНет в build-dependsДобавить пackage в .cabal
Plan failedGHC too new/oldПроверить tested-with
Stack Snapshot does not haveПакет вне LTSextra-deps

cabal build -v3 и stack build --verbose — первый шаг диагностики.


Интеграция с IDE

IDECabalStack
VS Code + Haskell extensionhls через cabal haddockhls + stack
IntelliJограниченноредко

Haskell Language Server (HLS) читает hie.yaml или cabal/stack metadata. Генерация:

stack exec hscaffold hie

Практикум: добавить тесты в существующий проект

1. Секция в .cabal

test-suite my-app-test
type: exitcode-stdio-1.0
main-is: Spec.hs
build-depends: base, my-app, tasty, tasty-hunit
hs-source-dirs: test
default-language: Haskell2010

2. test/Spec.hs

import Test.Tasty
import Test.Tasty.HUnit

main = defaultMain tests

tests = testGroup "Lib"
[ testCase "parse ok" $ parseNote "{\"title\":\"x\",\"done\":false}" @?= Right (Note "x" False)
]

3. Запуск

cabal test
# или stack test

Связь с монадическим кодом

Сборка не меняет архитектуру монады: IO остаётся в main, парсинг — в Either. Зависимости aeson, mtl подключаются один раз в .cabal — см. пример JSON выше в статье.


Чек-лист production-ready Haskell app

  • cabal freeze или Stack LTS зафиксирован в репозитории
  • CI: build + test на целевом GHC
  • -Wall без игнорирования предупреждений в CI
  • Executable static или Docker image
  • README с командами cabal run / stack exec
  • Версия GHC в tested-with

Дальше: простые приложения — HTTP, JSON, полный цикл.



Практикум — шаг 9: multi-package cabal.project

packages:
app
lib
api

Монорепо: общая библиотека, executable, test-suite.


Практикум — шаг 10: benchmark stanza

benchmark my-bench
type: exitcode-stdio-1.0
main-is: Bench.hs
build-depends: my-app, criterion
cabal bench

Практикум — шаг 11: flags в .cabal

flag dev
default: False
manual: True

if flag(dev)
ghc-options: -O0
else
ghc-options: -O2

Практикум — шаг 12: Nix + Cabal (обзор)

Nix даёт hermetic GHC; Cabal собирает внутри nix-shell. Для команд с воспроизводимым toolchain.


Воркшоп Cabal (45 мин)

МинЗадача
0–10cabal init
10–20Lib + exe
20–30aeson add
30–40hspec test
40–45freeze

Troubleshooting Cabal/Stack

СимптомРешение
cannot satisfycabal update, bounds
Module not foundbuild-depends
HLS mismatchhie.yaml, ghc version
Cyclic importsтретий модуль

FAQ Cabal

Cabal и Stack вместе? — выберите один.

Stackage? — курируемый snapshot.

hpack? — YAML → .cabal.

Артефакты? — dist-newstyle, .stack-work в gitignore.

Обновление deps? — cabal update / stack upgrade.



Дополнительные сценарии (Cabal/Stack)

Сценарий A: cabal init non-interactive

cabal init -n --is-executable
cabal build && cabal run

Сценарий B: stack new и сравнение

stack new stack-demo
diff -ru my-app.cabal stack-demo/package.yaml # структура

Сценарий C: freeze и чистая машина

cabal freeze
git add cabal.project.freeze

Сценарий D: hspec красный → зелёный

Намеренно сломайте greet — тест падает — исправьте.

Сценарий E: HLS в редакторе

:type greet в подсказках после hie.yaml.


Упражнения — контрольная точка

  1. Добавьте benchmark с criterion.
  2. flag(dev) для -O0 в dev-сборке.
  3. test-suite с exitcode-stdio.
  4. fourmolu в CI.
  5. Сравнение с npm.
Содержание