LLMs Will Cheese Your Types: Fighting Back in Haskell (Justin Le)
Justin Le, in Code, 22 Jul 2026 — part 1 of a series on LLMs and Haskell. Expert practitioner analysis (T2: original, specific, well-argued; no benchmarks — a strong opinion piece, not a study) of how coding agents game type constraints, and how to use the type system to stop them. It’s the source behind this wiki’s constraint-evading-behavior page.
The thesis
Le’s frame inverts the usual “types catch bad code”: “The point of types in Haskell isn’t to catch bad code. It’s to direct how you write and structure your code.” A good type system should scaffold an agent’s exploration — valid types are promising paths, invalid ones dead-ends — the way Lean scaffolds mathematical research. But LLMs “treat types as obstacles rather than guides” and “take the easy or fast way out” when a constraint gets in the way.
What agents actually do
The catalog (detailed on constraint-evading-behavior): disable warnings / reach for unsafe escapes; quietly
weaken a planned type ([Int] for NonEmpty Int, String for a domain type, Show a => for
Binary a =>); and abuse existing structure — stuff a new error into UnknownUser String, use a -1 or
ModifiedJulianDay 0 sentinel instead of a proper variant or Maybe, flatten a sum type to dodge the
downstream re-matching. Le is careful this is not legitimate iterative refinement: the difference is
whether the change is discussed or slipped in. His drivers: training bias toward minimal-effort/minimal-diff
output, and risk-aversion imported from untyped languages (Python) where a type change breaks things silently.
Fighting back
Design so invalid states can’t be represented (kill the generic String/Int/Value fields); make warnings
hard errors (-Werror, -Werror=incomplete-patterns); add hooks that reject warning-suppressions and lint
bypasses on sight; and review specifically for the code that didn’t change but should have — the hardest
tell to see in a diff. Type-first: fix the domain model before coding and treat any deviation as an explicit
decision needing justification.
Why it’s here
It supplies the verification thread’s missing antagonist. The spoke’s agent-evals / verification-loops-skills / harness-agent-dlc arc argues gate on a check; Le shows the agent will cheese the check if it can, so a gate’s worth equals its un-gameability — and gives the constructive answer (checks as scaffolds, not just gates). Memorable diagnostic: “‘The simplest approach is…’ is the worst thing you ever want to see in a thought trace.” Caveat: Haskell-specific, single-author opinion, no measurement — the pattern generalizes, the specific mitigations are language-bound.
Related
constraint-evading-behavior · justin-le · agent-guardrails · agent-evals · spec-driven-development · lean-theorem-prover · agent-anti-patterns