Skip to content

ADR-007: Validation Error Accumulation via Applicative Functor

Context

The compiler runs multiple validation checks — exhaustiveness, evolve totality, guard consistency. These checks can either fail-fast (stop at first error) or accumulate all errors and report them together.

The question is how to compose independent checks and which checks can run in parallel.

StrategyErrors reportedComposition
Monad (Either)First error onlySequential (>>=)
Applicative (Validation)All errorsParallel (<*>)

Source: specs/theory.allium — “Applicative Functor (validation composition)” section (Th-3).

Decision

Use Validation<List<Diagnostic>, A> — the applicative functor, not a monad.

Independent checks (exhaustiveness, totality, guard consistency) compose via applicative <*> without sequencing — they can execute in any order or in parallel. Dependent checks (postconditions depend on evolve totality) require monadic bind and run only after their prerequisites succeed.

The first iteration ships with stop_on_first_error: true (configured in core.allium). Full applicative accumulation is the target steady state and is enabled progressively as the validation pipeline stabilises.

Consequences

Positive

  • Domain modelers see all errors at once rather than fixing one at a time.
  • The applicative vs. monadic distinction explicitly documents which checks depend on which, making the dependency structure visible in code.
  • Independent checks can execute concurrently without coordination overhead.

Negative

  • stop_on_first_error: true in the first iteration means accumulation is not yet active; the architecture is in place but the user-facing benefit is deferred.
  • The applicative/monadic distinction adds conceptual complexity for contributors unfamiliar with functional programming abstractions.