Evidence

Controlled baseline — everything held constant except explicit domain types

The most tightly controlled comparison in this evidence base, and the one that contradicts the easy conclusion. It is published because it did.

What this was

Status
Complete — and it refuted the hypothesis it was built to test
Repository
kemiller2002/time-entry-state-machine
Branch
main
Ending commit
73a63f1ac319
Executed by
One session, which had already designed and implemented the change once in F# before the trial began.

Objective

For the same conceptual change, compare a codebase with explicit domain types against an otherwise-competent conventional implementation — holding feature scope, code quality, the change itself, and the agent constant.

Hypothesis

Explicit domain types reduce the cost of propagating a change. Falsification was preregistered: if the conventional baseline needed equal or fewer edits and surfaced equal or fewer stale-call-site defects, the hypothesis would not be supported.

Method

A conventional TypeScript baseline was written to reflect competent practice — shared validation, immutable updates, a discriminated result type, real tests — with the single removed ingredient being opaque domain types for the value that changes meaning. The identical conceptual change was then applied to it and compared against the same change already made to the explicitly-typed F# domain.

Recorded metrics

none measurable

Difference between type systems on field renames

Instrumented

Measures
Whether an explicitly-typed domain propagated a field rename more completely than a competent conventional baseline.
Collected by
The identical change was applied to both and every call site counted. Both caught every site automatically.
Does not show
This is a null result, published because the experiment was designed to be falsifiable. It does not mean types are useless; it means this particular change class does not distinguish them.
Sourcekemiller2002/time-entry-state-machine / research/experiments/EX-TE-2026-0001--conventional-baseline-comparison.md at 73a63f1ac319 — Analysisprivate repository

none

Help from either type system on arithmetic business rules

Instrumented

Measures
Whether a wrong unit conversion embedded in an expression was caught by either implementation's type system.
Collected by
The same change was applied to both. A wrong divisor compiles cleanly in both, because the value is a bare number until it crosses a smart constructor.
Does not show
This directly contradicts the stronger form of the hypothesis the experiment tested. It is on this site because it is true, not because it helps.
Sourcekemiller2002/time-entry-state-machine / research/experiments/EX-TE-2026-0001--conventional-baseline-comparison.md at 73a63f1ac319 — Analysisprivate repository

1 of 7

Conversion sites a pattern search missed

Instrumented

Measures
Sites needing change that a grep for the conversion pattern failed to find, because the same rounding was written as a division by 360 rather than by 6.
Collected by
Found only by a second, deliberate search prompted by suspicion that the first pass was not exhaustive.
Does not show
The report traces this to inlined conversions rather than to the type system, and notes the centralising discipline that avoided it is achievable in either language.
Sourcekemiller2002/time-entry-state-machine / research/experiments/EX-TE-2026-0001--conventional-baseline-comparison.md at 73a63f1ac319 — Resultsprivate repository

Findings

What this refuted

Anomalies, failures and confounders

Limitations

Interpretation

This is the most carefully controlled experiment in the evidence base and it went against the hypothesis. That combination is why it matters more than the trials that agreed.

The design was honest before the result was known. Falsification criteria were written in advance: if the conventional baseline needed equal or fewer edits and surfaced equal or fewer stale call sites, the hypothesis would not be supported. The baseline was deliberately competent — shared validation, immutable updates, a discriminated result type, real tests — with the single removed ingredient being opaque types for the value that changes meaning.

The result splits cleanly in two, and the split is the finding. Field renames are a structural change, and both type systems caught every affected site automatically and completely; there was nothing to choose between them. Arithmetic embedded in an expression is not a structural change, and neither type system helped at all, because the value is an ordinary number right up until it crosses a smart constructor — and the arithmetic sits upstream of that boundary in both designs.

The one asymmetry that did appear was almost mistaken for a type-system result. A pattern search for the conversion missed a site where the same rounding had been written as a division by 360 rather than by 6, and it was recovered only by a second search prompted by suspicion. It would have been easy to file that under "explicit types win." The report does not: it traces the difference to the explicitly-typed domain having routed its arithmetic through named accessors instead of inlining the conversion, and states that the same discipline was achievable in the baseline and arguably should have been applied there.

So the conclusion is narrower than the hypothesis wanted, and more useful than it would have been: centralising a conversion is what avoided the missed site, not nominal typing. Those are different interventions with different costs, and this experiment is the reason we can tell them apart.

It also leaves a real gap it names itself. Comparing F# against TypeScript does not separate language from type system. The trial that would — a primitive-typed version of the same language against an explicitly-typed one — has not been run.

Source artifacts