# Haskell

> Entry point and roadmap for any Haskell development work. Use this skill whenever the user mentions Haskell, cabal, GHC, GHCi, HLS, GHCup, mise, Hackage, `.hs`/`.cabal`/`mise.toml`/`.tool-versions` files, or otherwise begins a Haskell-related task — even when no specific sub-topic is yet active. Loads the map of focused haskell-* sub-skills (project setup, devcontainer, native mise+GHCup toolchain, type design, domain errors, effectful, database, json, logging, testing, documentation, benchmarking, quality gates, debugging) plus the cross-cutting defaults that hold across all of them. Acts as the orientation layer so the specific sub-skills don't each need to win a trigger battle on their own.

- Skill: `ivelten/haskell` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ivelten/haskell`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ivelten/haskell/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Product & Planning
- Author: ivelten (https://skillmd.com/u/ivelten)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ivelten/haskell

---


# Haskell Development (Entry Point)

Orientation skill for any Haskell work. The opinionated stack and conventions are partitioned across focused sub-skills; this is the map that tells you which to load when. Don't try to recall sub-skill content from this map — invoke them on demand via the Skill tool.

## Sub-skill map

| Sub-skill | Invoke when working on |
|---|---|
| `haskell-devcontainer` | Detecting or setting up the pre-built devcontainer (`ivelten/haskell-devcontainer`), `.devcontainer/` files, VS Code extensions, direnv inside the container |
| `haskell-toolchain-mise` | Provisioning the toolchain natively on the host: `mise.toml` pins, GHC/HLS via GHCup, HLS↔GHC compatibility, PATH and shims, `.envrc` with `use mise` |
| `haskell-project-setup` | Bootstrapping a repo: `.cabal`/`cabal.project`, project layout, language extensions, Hoogle, fast-tags, direnv |
| `haskell-type-design` | Modeling domain types, newtypes, sum types, "parse don't validate", phantom types, records, field naming |
| `haskell-domain-errors` | Designing error ADTs, `Show`/`ToJSON`/`Exception` instances, choosing `Either` vs `Error` effect vs `throwIO` |
| `haskell-effectful` | Application architecture, defining effects, writing interpreters (production + in-memory), composition in `main` |
| `haskell-database` | PostgreSQL modeling with Rel8, type-mapping decisions (composite vs jsonb vs enum), HKD table definitions, queries, migrations |
| `haskell-json` | Aeson `ToJSON`/`FromJSON`: `TaggedObject` discriminator for sum types, `fieldLabelModifier`, manual instances for non-record constructors |
| `haskell-logging` | Adding `log-effectful`, choosing severities, structured payloads, `localData` scoping |
| `haskell-testing` | hspec setup, QuickCheck properties, hspec-discover, colored output, testing effectful code |
| `haskell-documentation` | Haddock conventions, doctest setup, the Haddock coverage gate, fixing broken Haddock references |
| `haskell-benchmarking` | tasty-bench/weigh, regression detection, GHC profiling, space-leak hunting |
| `haskell-quality-gates` | `ormolu`, `hlint`, `cabal-gild`, pre-commit hooks, CI config, performance defaults, the "done" checklist |
| `haskell-debugging` | GHCi debugger (`:break`, `:step`, `:trace`), DAP setup (haskell-dap/ghci-dap/haskell-debug-adapter), VS Code launch.json |

## Environment selection (first decision on a fresh project)

Before any build config, the project needs a toolchain. Run both detections first — only ask if neither fires:

```bash
grep -q "ivelten/haskell-devcontainer" .devcontainer/docker-compose.yml 2>/dev/null && echo "devcontainer"
ls mise.toml .mise.toml 2>/dev/null && echo "native (mise)"
```

If neither is configured, ask which the user wants, with the trade-off:

- **Native, `mise` + GHCup** → `haskell-toolchain-mise`. No Docker, no VM overhead — the difference is large on macOS, where every container is a Linux VM. Costs a one-time toolchain install per machine.
- **Devcontainer** → `haskell-devcontainer`. Zero-setup, disposable, matches a Linux CI, and hosts Docker services (Postgres) next to the app. Costs virtualization overhead on macOS.

Neither is the default. Ask, then load the matching sub-skill; `haskell-project-setup` handles everything downstream of that choice.

## Routing rules

- When a task spans multiple topics, invoke sub-skills in the order they're needed. A fresh project starts with the environment selection above, then `haskell-project-setup`, then others as features arrive.
- Don't preemptively load all sub-skills at session start. The map above is the orientation; load each one on demand when its topic actually surfaces.
- If a sub-skill is already loaded in this session, don't reload it.
- The specific sub-skills' own descriptions may also fire automatically on later messages — let them. This umbrella complements those triggers, it doesn't replace them.

## Defaults that hold regardless of which sub-skill is active

If you have to write Haskell before any specific sub-skill has loaded, these are the floor:

- **Stack**: `cabal` + GHCup-provisioned GHC 9.10.3 (via devcontainer or `mise` natively), `effectful` for effects, `hspec` + `QuickCheck` for tests, `ormolu` + `hlint` for hygiene, Haddock + doctest for docs.
- **Pin GHC from the HLS support list**, never the reverse — HLS bindists target specific GHC patch versions (9.10.3 is the only 9.10.x supported by HLS 2.14).
- **Do not use** `stack`, Nix shells, `mtl`, `polysemy`, or `fused-effects` unless the user explicitly asks.
- **Total functions, strict fields, `Text` not `String`, explicit export lists, Haddock on every public binding.**

Detail for each of these lives in the relevant sub-skill; load it when you reach for the topic.

