Skip to Content
🇫🇷 🇪🇺 Looking for experienced guidance on Lean software delivery? We'd be glad to explore how we can work together. Contact →
Advanced Software EngineeringTypes and LogicThe Curry-Howard Correspondence: Linking Programs and Proofs

The Curry-Howard Correspondence: Linking Programs and Proofs

The Curry-Howard correspondence, sometimes called the Curry-Howard isomorphism, establishes a deep equivalence between two apparently distinct domains: formal logic and type theory. The term isomorphism here means that these two domains have exactly the same structure: they are two ways of looking at the same thing.

The Foundational Isomorphism

Discovered independently by Haskell Curry in the 1930s and William Alvin Howard in 1969, this correspondence reveals that types are logical propositions and programs are proofs of those propositions.

A program that compiles is thus literally a demonstration that its type is inhabited, meaning there exists at least one value satisfying this specification. This duality is not a mere poetic analogy; it’s a rigorous structural identity.

Correspondences Between Types and Logical Propositions

The elementary correspondences between types on one hand and logical propositions on the other are elegant in their simplicity:

TypeLogicInterpretation
A×BA \times BABA \land BTo prove “A and B”, provide a proof of A and a proof of B
ABA \mid BABA \lor BA proof of “A or B” is either a proof of A, or a proof of B
ABA \to BABA \Rightarrow BA proof of “A implies B” transforms a proof of A into a proof of B
void\botContradiction (zero inhabitants)
unit\topTrivial truth (one inhabitant)

This similarity of structure, clearly apparent in the table, is what constitutes the isomorphism1 itself.

See also: Type Algebra: Products and Sums

The never Type and Contradiction

This correspondence illuminates the role of the never type in TypeScript or Nothing in Scala.

A function of type AneverA \to \text{never} asserts “if A is true, then there’s a contradiction”: in other words, A is false, A is uninhabited (= A contains no value). This is exactly the principle of ex falso quodlibet: from falsehood, anything can be derived.

When the compiler infers that an expression’s type is never, it proves that this execution path is impossible. Exhaustive pattern matching analyses exploit this mechanism: if all cases are covered, the residual case is of type never, proving that no case was forgotten.

Polymorphic Types and Quantifiers

Polymorphic types2 correspond to logic quantifiers.

The universal quantifier \forall, pronounced “for all”: a function of type A.AA\forall A. A \to A (meaning “for all types A, a function from A to A”) must work for any type A.

function identity<A>(x: A): A { return x // only possible implementation }

This function cannot inspect what A is, nor make special cases: it’s the caller who chooses. The type constrains the implementation to the point of determining it entirely.

The existential quantifier \exists, pronounced “there exists”: a type like A.(A,AString)\exists A. (A, A \to \text{String}) means “there exists a type A, and I provide a value of that type plus a function to convert it.”

interface Showable { // A is hidden - caller doesn't know what it is show: () => string } const myShowable: Showable = { // implementer chooses A = number show: () => String(42) }

Here it’s the implementer who chooses A, and the caller doesn’t know what it is: this is the foundation of abstract types and encapsulation.

This correspondence extends to higher-order logics with dependent types: a type like n:Nat.VectornVectorn\forall n : \text{Nat}. \text{Vector}\langle n \rangle \to \text{Vector}\langle n \rangle, read “for all natural numbers n, a function from a vector of size n to a vector of size n,” expresses a proposition quantifying over values, not just types.

Proof assistants like Coq , Agda , or Lean  fully exploit this equivalence: programs written there are proofs of mathematical theorems.

Practical Implications in Everyday Programming

The Curry-Howard correspondence seems very theoretical and abstract, but the practical implications for everyday developers are real:

  • Designing a function signature is stating a theorem
  • Implementing that function is proving it
  • A type that’s too weak (like any) corresponds to a trivial proposition that asserts nothing interesting
  • A type impossible to implement reveals a contradictory specification

Concretely, for a TypeScript or Python developer: when the compiler refuses your code with a type error, it’s telling you your “proof” is invalid. And when you choose unknown rather than any, you commit to proving that you’ve correctly identified the type before using it.

Wadler’s free theorems  show that certain polymorphic types have only one possible implementation: the type is the complete specification.

The Curry-Howard correspondence thus invites us to see typing not as bureaucratic constraint, but as a formal reasoning system where every program that compiles constitutes a mechanically verified proof. It’s, in a way, the developer’s poka-yoke : a mechanism that makes certain errors structurally—mathematically, in reality—impossible.

Footnotes

  1. From Greek: iso (ἴσος, equal) + morphe (μορφή, form) = “same form.”

  2. The term comes from Greek: poly (many) + morphe (form) = “many forms.”

Want to dive deeper into these topics?

We help teams adopt these practices through hands-on consulting and training.

or email us at contact@evryg.com

Last updated on