Software correctness and programming languages with Sean Allen

The Pragmatic Engineer • • 1h25 → 4 min • #111
Software correctness and programming languages with Sean Allen
Watch on YouTube

Summary

  • Sean Allen, a software engineer on the Red Team at Antithesis, discusses his journey from music to programming, the design of programming languages that prevent bugs by construction, and a philosophy of software correctness that prioritizes static verification over late-stage testing.

Background and path into programming

  • Allen graduated with no clear direction, moved to New York City, and got a job fixing computers at Scholastic before a friend suggested learning Perl to join a startup in Boston.
  • The startup collapsed, but he discovered he enjoyed programming and could earn well doing it; his first exposure was a Commodore 64 machine-language book his grandmother bought, which made programming seem impossibly hard.
  • He invested heavily in self-study, gravitating toward BeOS and C++; the head of compiler tools at Metrowerks and sci-fi author Neal Stephenson (then active on mailing lists) mentored him in C++.
  • He later chose C and Ada as his two focus languages: C for Mac OS development, Ada for its rigorous static checking aimed at mission-critical systems (defense, NASA).
  • Ada’s strictness frustrated him — his programs wouldn’t compile — while C programs compiled but segfaulted; years later he realized he’d rather have the compiler enforce the rules he’d painstakingly developed for safe C/C++ concurrency.

Antithesis and the Red Team

  • Will Wilson (CEO) recruited Allen via a “Who’s Hiring” post; the role was essentially “do Jepsen-style things” — i.e., fault-injection testing of distributed systems.
  • The Red Team uses the product exactly as customers do, with no internal tooling privileges, to surface usability gaps and teach both customers and colleagues how to build reliable software.
  • Allen’s focus is on correctness techniques that start “way down the stack,” long before anything called testing.

Programming languages that enforce correctness by construction

  • OCaml: descended from ML; combines functional programming with object-oriented features (the “O”), though almost no production OCaml code uses the O features. Its sum types, pattern matching, and exhaustive matching let the compiler flag every unhandled case when a type changes — a “gloriously wonderful experience.”
  • F#: Microsoft Research’s “OCaml for .NET,” created by Don Syme. Its distinction is seamless .NET interop without the rough edges common on the JVM (e.g., Scala/Java). F# and .NET co-evolved symbiotically; many F# features later migrated to C#, with exhaustive matching on sum types the main remaining gap.
  • Pony: open-source, actor-model language co-created by Allen’s college friend Sylvan Clebsch (PhD at Imperial College). Designed for fast, highly asynchronous, concurrent programs with compiler-enforced memory safety — “fearless concurrency” baked in, not assembled from libraries.
    • Each actor has its own heap; garbage collection runs per-actor between work units, eliminating stop-the-world pauses and yielding flat tail latencies (trade-off: slightly lower peak throughput).
    • The type system makes illegal states unrepresentable, similar to Rust but with a built-in actor model that simplifies reasoning about concurrency.
  • Ada: still used in high-assurance domains; its selling point against Rust is “more guarantees.”
  • Rust: heavily ML-influenced (sum types, pattern matching); provides strong static guarantees but lacks a built-in concurrency model — you compose your own.

Software correctness: testing as a means, not an end

  • Allen defines correctness as “my program does what I expected it to do” — upholding invariants, not waking anyone at 2 a.m.
  • Tests are a means to an end; the end is correctness. Testing happens late; better to encode invariants in the type system (sum types, exhaustive matching, legal state transitions) so the compiler prevents whole classes of bugs before tests run.
  • TDD is excellent for API design, but example-based tests can be defeated by hard-coded implementations; property-based testing (randomized inputs) forces genuine correctness.
  • Workloads: the combination of system under test, validation logic, and input generation that drives interesting behavior. A naive “drunken walk” of random operations rarely hits edge cases (e.g., 10,000 puts in a row triggering resharding).
    • Bank test: simple workload for isolation guarantees — transfer money between accounts in transactions while a concurrent reader checks the total never deviates from $10,000. Under fault injection (network partitions, node kills), subtle bugs surface.
    • Workloads are not scripted scenarios; they define rules and let the generator explore.

Quality is not a gate

  • The “gate” metaphor assumes quality is kept by catching bad things at the door. In reality, every layer (code review, CI, staging, observability) has holes; production is always the final test.
  • High-functioning teams build correctness in from the start: formal specs (TLA+, Alloy), strong type systems, early fault-injection testing (Jepsen/Antithesis) on the first version, and designing for observability.
  • Low-functioning teams rely on late gates (PR checks, staging) and move slowly or ship bugs.
  • Business logic can be statically constrained (e.g., state machines in the type system), though no language eliminates all need for testing — performance and ecosystem trade-offs force compromises.

Supply chain security

  • Attack the weakest link: compromise a popular dependency (e.g., left-pad on npm, malicious PyPI packages) to inject code that runs with the same privileges as the application.
  • Naive defenses (cool-down periods, vendoring, forking) fail: cool-downs delay security fixes; vendoring/forking doesn’t scale; someone must still audit the code.
  • Root cause: almost every language’s package model (inherited from CPAN) assumes a trusting, good-actor ecosystem. Code from the internet has full ambient authority (network, filesystem, crypto miners).
  • Object capabilities (e.g., language E, and built into Pony) require explicit capabilities for any side effect (network, FS); the host program delegates only what’s needed. But FFI (C/Rust interop) bypasses this unless also capability-controlled.
  • Confidential computing (encrypted VMs, attestation) protects data from cloud operators, but speculative execution side channels (Spectre/Meltdown family) break all guarantees; mitigations cost 50%+ performance, so the industry plays whack-a-mole rather than disable speculation.

Technical debt: not all debt must be repaid

  • “Technical debt only hurts if you have to pay it back.” Teams often sidestep debt: throw money at a managed service (e.g., distributed SQL), delete the system when the business pivots, or never hit the scaling limit.
  • Debt becomes a trap only when it prevents necessary changes before product-market fit.
  • The “all debt is bad” dogma doesn’t match reality; successful companies pay debt because they succeeded, not because debt is inherently fatal.

Go slow to go fast

  • Counterintuitive practices that accelerate delivery:
    • Write a planning doc and agree on scope before coding.
    • Define how to test/observe the system before building.
    • Spike unknowns for a few hours/days.
    • Stop and fix when production is on fire at 2 a.m. (rested brains make fewer mistakes).
    • Align vocabulary: weeks of misaligned work stem from never defining terms (e.g., “exactly once” meaning delivery vs. processing).
    • Design for observability and testability up front — gray failures (bad UX, healthy load balancer) are invisible without deliberate instrumentation.
Back to The Pragmatic Engineer