Mark Seemann’s “Worse is better” series is about technologies that won despite better alternatives — VHS over Betamax. This entry runs the argument on the two languages he knows best, and lands somewhere uncomfortable for the more popular one.
Comparing C# and F# is unusually clean, he argues, because both run on .NET: same web servers, same ADO.NET providers, same hosting options. Hold the ecosystem constant and only the languages are left to judge. His verdict: F# is better on most axes, and it is the least popular of Microsoft’s three supported .NET languages — behind even VB.NET.
- F# arrived in 2005, after C#, so by design it had to match C# feature-for-feature or interop would break. Then it kept going: computation expressions, strong type inference, structural equality on collections, language-level dependency management
- C# keeps catching up, which narrows the gap without closing it. Discriminated unions are coming; some things still do not translate
- The feature he doubts C# will ever get: circular dependencies are disallowed by default, so dependency direction is enforced by the compiler rather than by module boundaries and senior vigilance
- “Junior developers don’t understand dependency management, and seniors have to be constantly vigilant to prevent code rot.” Decomposing a codebase into modules is the coarse-grained workaround that F# does at the language level
- C# still wins on hiring pool, Razor assuming C# or VB.NET, IDE support, and the tooling gaps he concedes — cyclomatic complexity, coverage, and mutation testing have all been painful for F#
- He would still pick F# by default for a greenfield .NET application, caveats named rather than hidden
Worth reading as a template for arguing about tooling without pretending popularity and quality are the same variable. The constraint normally treated as an annoyance — no circular dependencies, and the file ordering that follows — is really an architectural guarantee that ships with the compiler.
His point about maturity cuts both ways, and he says so: the language came later, so it had to be compatible, and the tooling is younger. The interesting claim is that a compiler can hold an architectural line that a team otherwise holds by convention.