# The Curry-Howard Correspondence

## Contents

# The Curry-Howard Correspondence¶

Note

A *lagniappe* is a small and unexpected gift — a little “something extra”.
Please enjoy this little chapter, which contains one of the most beautiful
results in the entire book. It is based on the paper
Propositions as Types by Philip Wadler. You can watch an entertaining
recorded lecture by Prof. Wadler on it, in addition
to our lecture below.

As we observed long ago, OCaml is a language in the ML family, and ML was
originally designed as the __m__eta __l__anguage for a theorem
prover—that is, a computer program designed to help prove and check the
proofs of logical formulas. When constructing proofs, it’s desirable to make
sure that you can only prove true formulas, to make sure that you don’t make
incorrect arguments, etc.

The dream would be to have a computer program that can determine the truth or
falsity of any logical formula. For some formulas, that is possible. But, one of
the groundbreaking results in the early 20th century was that it is *not*
possible, in general, for a computer program to do this. Alonzo Church and Alan
Turing independently showed this in 1936. Church used the *lambda calculus* as a
model for computers; Turing used what we now call *Turing machines*. The
*Church-Turing thesis* is a hypothesis that says the lambda calculus and Turing
machines both formalize what “computation” informally means.

Instead of focusing on that impossible task, we’re going to focus on the relationship between proofs and programs. It turns out the two are deeply connected in a surprising way.

## Computing with Evidence¶

We’re accustomed to OCaml programs that manipulate data, such as integers and
variants and functions. Those data values are always typed: at compile time,
OCaml infers (or the programmer annotates) the types of expressions. For
example, `3110 : int`

, and `[] : 'a list`

. We long ago learned to read those as
“`3110`

has type `int`

”, and “`[]`

has type `'a list`

”.

Let’s try a different reading now. Instead of “has type”, let’s read “is
evidence for”. So, `3110`

is evidence for `int`

. What does that mean? Think of a
type as a set of values. So, `3110`

is evidence that type is not empty.
Likewise, `[]`

is evidence that the type `'a list`

is not empty. We say that the
type is *inhabited* if it is not empty.

Are there empty types? There actually is one in OCaml, though we’ve never had reason to mention it before. It’s possible to define a variant type that has no constructors:

```
type empty = |
```

```
type empty = |
```

We could have called that type anything we wanted instead of `empty`

; the
special syntax there is just writing `|`

instead of actual constructors. (Note,
that syntax might give some editors some trouble. You might need to put
double-semicolon after it to get the formatting right.) It is impossible to
construct a value of type `empty`

, exactly because it has no constructors. So,
`empty`

is not inhabited.

Under our new reading based on evidence, we could think about functions as ways to manipulate and transform evidence—just as we are already accustomed to thinking about functions as ways to manipulate and transform data. For example, the following functions construct and destruct pairs:

```
let pair x y = (x, y)
let fst (x, y) = x
let snd (x, y) = y
```

```
val pair : 'a -> 'b -> 'a * 'b = <fun>
```

```
val fst : 'a * 'b -> 'a = <fun>
```

```
val snd : 'a * 'b -> 'b = <fun>
```

We could think of `pair`

as a function that takes in evidence for `'a`

and
evidence for `'b`

, and gives us back evidence for `'a * 'b`

. That latter piece
of evidence is the the pair `(x, y)`

containing the individual pieces of
evidence, `x`

and `y`

. Similarly, `fst`

and `snd`

extract the individual pieces
of evidence from the pair. Thus,

If you have evidence for

`'a`

and evidence for`'b`

, you can produce evidence for`'a`

and`'b`

.If you have evidence for

`'a`

and`'b`

, then you can produce evidence for`'a`

.If you have evidence for

`'a`

and`'b`

, then you can produce evidence for`'b`

.

In learning to do proofs (say, in a discrete mathematics class), you will
have learned that in order to prove two statements hold, you individually
have to prove that each holds. That is, to prove the conjunction of A and B,
you must prove A as well as prove B. Likewise, if you have a proof of the
conjunction of A and B, then you can conclude A holds, and you can conclude B
holds. We can write those patterns of reasoning as logical formulas, using
`/\`

to denote conjunction and `->`

to denote implication:

```
A -> B -> A /\ B
A /\ B -> A
A /\ B -> B
```

Proofs are a form of evidence: they are logical arguments about the truth of a statement. So another reading of those formulas would be:

If you have evidence for A and evidence for B, you can produce evidence for A and B.

If you have evidence for A and B, then you can produce evidence for A.

If you have evidence for A and B, then you can produce evidence for B.

Notice how we now have given the same reading for programs and for proofs. They
are both ways of manipulating and transforming evidence. In fact, take a close
look at the types for `pair`

, `fst`

, and `snd`

compared to the logical formulas
that describe valid patterns of reasoning:

```
val pair : 'a -> 'b -> 'a * 'b A -> B -> A /\ B
val fst : 'a * 'b -> 'a A /\ B -> A
val snd : 'a * 'b -> 'b A /\ B -> B
```

If you replace `'a`

with A, and `'b`

with B, and `*`

with `/\`

, **the types of
the programs are identical to the formulas!**

## The Correspondence¶

What we have just discovered is that computing with evidence corresponds to
constructing valid logical proofs. This correspondence is not just an accident
that occurs with these three specific programs. Rather, it is a deep phenomenon
that links the fields of programming and logic. Aspects of it have been
discovered by many people working in many areas. So, it goes by many names. One
common name is *the Curry-Howard correspondence*, named for logicians Haskell
Curry (for whom the functional programming language Haskell is named) and
William Howard. This correspondence links ideas from programming to ideas from
logic:

Types correspond to logical formulas (aka

*propositions*).Programs correspond to logical proofs.

Evaluation corresponds to simplification of proofs.

We’ve already seen the first two of those correspondences. The types of our three little programs corresponded to formulas, and the programs themselves corresponded to the reasoning done in proofs involving conjunctions. We haven’t seen the third yet; we will later.

Let’s dig into each of the correspondences to appreciate them more fully.

## Types Correspond to Propositions¶

In *propositional logic*, formulas are created with atomic propositions,
negation, conjunction, disjunction, and implication. The following BNF describes
propositional logic formulas:

```
p ::= atom
| ~ p (* negation *)
| p /\ p (* conjunction *)
| p \/ p (* disjunction *)
| p -> p (* implication *)
atom ::= <identifiers>
```

For example, `raining /\ snowing /\ cold`

is a proposition stating that it is
simultaneously raining and snowing and cold (a weather condition known as
*Ithacating*). An atomic proposition might hold of the world, or
not. There are two distinguished atomic propositions, written `true`

and
`false`

, which are always hold and never hold, respectively.

All these *connectives* (so-called because they connect formulas together) have
correspondences in the types of functional programs.

**Conjunction.** We have already seen that the `/\`

connective corresponds to
the `*`

type constructor. Proposition `A /\ B`

asserts the truth of both `A`

and
`B`

. An OCaml value of type `a * b`

contains values both of type `a`

and `b`

.
Both `/\`

and `*`

thus correspond to the idea of pairing or products.

**Implication.** The implication connective `->`

corresponds to the function
type constructor `->`

. Proposition `A -> B`

asserts that if you can show that
`A`

holds, then you can show that `B`

holds. In other words, by assuming `A`

,
you can conclude `B`

. In a sense, that means you can transform `A`

into `B`

. An
OCaml value of type `a -> b`

expresses that idea even more clearly. Such a value
is a function that transforms a value of type `a`

into a value of type `b`

.
Thus, if you can show that `a`

is inhabited (by exhibiting a value of that
type), you can show that `b`

is inhabited (by applying the function of type
`a -> b`

to it). So, `->`

corresponds to the idea of transformation.

**Disjunction.** The disjunction connective `\/`

corresponds to something a
little more difficult to express concisely in OCaml. Proposition `A \/ B`

asserts that either you can show `A`

holds or `B`

holds. Let’s strengthen that
to further assert that in addition to showing *one* of them holds, you have to
specify *which one* you are showing. Why would that matter?

Suppose we were working on a proof of the *twin prime conjecture*, an unsolved
problem that states there are infinitely many twin primes (primes of the form
\(n\) and \(n+2\), such as 3 and 5, or 5 and 7). Let the atomic proposition `TP`

denote that there are infinitely many twin primes. Then the proposition
`TP \/ ~ TP`

seems reasonable: either there are infinitely many twin primes, or
there aren’t. We wouldn’t even have to figure out how to prove the conjecture!
But if we strengthen the meaning of `\/`

to be that we have to state *which one*
of the sides, left or right, holds, then we would either have to give a proof or
disproof of the conjecture. No one knows how to do that currently. So we could
not prove `TP \/ ~ TP`

.

Henceforth we will use `\/`

in that stronger sense of having to identify whether
we are giving a proof of the left or the right side proposition. Thus, we can’t
necessarily conclude `p \/ ~ p`

for any proposition `p`

: it will matter whether
we can prove `p`

or `~ p`

on their own. Technically, this makes our
propositional logic *constructive* rather than *classical*. In constructive
logic we must construct the proof of the individual propositions. Classical
logic (the traditional way `\/`

is understood) does not require that.

Returning to the correspondence between disjunction and variants, consider this variant type:

```
type ('a, 'b) disj = Left of 'a | Right of 'b
```

```
type ('a, 'b) disj = Left of 'a | Right of 'b
```

A value `v`

of that type is either `Left a`

, where `a : 'a`

; or `Right b`

, where
`b : 'b`

. That is, `v`

identifies (i) whether it is tagged with the left
constructor or the right constructor, and (ii) carries within it exactly one
sub-value of type either `'a`

or `'b`

—not two subvalues of both types,
which is what `'a * 'b`

would be.

Thus, the (constructive) disjunction connective `\/`

corresponds to the `disj`

type constructor. Proposition `A \/ B`

asserts that either `A`

or `B`

holds as
well as which one, left or right, it is. An OCaml value of type `('a, 'b) disj`

similarly contains a value of type either `'a`

or `'b`

as well as identifying
(with the `Left`

or `Right`

constructor) which one it is. Both `\/`

and `disj`

therefore correspond to the idea of unions.

**Truth and Falsity** The atomic proposition `true`

is the only proposition that
is guaranteed to always hold. There are many types in OCaml that are always
inhabited, but the simplest of all of them is `unit`

: there is one value `()`

of
type `unit`

. So the proposition `true`

(best) corresponds to the type `unit`

.

Likewise, the atomic proposition `false`

is the only proposition that is
guaranteed to never hold. That corresponds to the `empty`

type we introduced
earlier, which has no constructors. (Other names for that type could include
`zero`

or `void`

, but we’ll stick with `empty`

.)

There is a subtlety with `empty`

that we should address. The type has no
constructors, but it is nonetheless possible to write expressions that have type
`empty`

. Here is one way:

```
let rec loop x = loop x
```

```
val loop : 'a -> 'b = <fun>
```

Now if you enter this code in utop you will get no response:

```
let e : empty = loop ()
```

That expression type checks successfully, then enters an infinite loop. So,
there is never any value of type `empty`

that is produced, even though the
expression has that type.

Here is another way:

```
let e : empty = failwith ""
```

```
Exception: Failure "".
Raised at Stdlib.failwith in file "stdlib.ml", line 29, characters 17-33
Called from unknown location
Called from Stdlib__Fun.protect in file "fun.ml", line 33, characters 8-15
Re-raised at Stdlib__Fun.protect in file "fun.ml", line 38, characters 6-52
Called from Topeval.load_lambda in file "toplevel/byte/topeval.ml", line 89, characters 4-150
```

Again, the expression type checks, but it never produces an actual value of type
`empty`

. Instead, this time an exception is produced.

So the type `empty`

is not inhabited, even though there are some expressions of
that type. But, **if we require programs to be total**, we can rule out those
expressions. That means eliminating programs that raise exceptions or go into an
infinite loop. We did in fact make that requirement when we started discussing
formal methods, and we will continue to assume it.

**Negation.** This connective is the trickiest. Let’s consider negation to
actually be syntactic sugar. In particular, let’s say that the propositional
formula `~ p`

actually means this formula instead: `p -> false`

. Why? The
formula `~ p`

should mean that `p`

does not hold. So if `p`

*did* hold, then it
would lead to a contradiction. Thus, given `p`

, we could conclude `false`

. This
is the standard way of understanding negation in constructive logic.

Given that syntactic sugar, `~ p`

therefore corresponds to a function type whose
return type is `empty`

. Such a function could never actually return. Given our
ongoing assumption that programs are total, that must mean it’s impossible to
even call that function. So, it must be impossible to construct a value of the
function’s input type. Negation therefore corresponds to the idea of
impossibility, or contradiction.

**Propositions as types.** We have now created the following correspondence that
enables us to read propositions as types:

`/\`

and`*`

`->`

and`->`

`\/`

and`disj`

`true`

and`unit`

`false`

and`empty`

`~`

and`... -> false`

But that is only the first level of the Curry-Howard correspondence. It goes deeper…

## Programs Correspond to Proofs¶

We have seen that programs and proofs are both ways to manipulate and transform
evidence. In fact, every program **is** a proof that the type of the program is
inhabited, since the type checker must verify that the program is well typed.

The details of type checking, though, lead to an even more compelling correspondence between programs and proofs. Let’s restrict our attention to programs and proofs involving just conjunction and implication, or equivalently, pairs and functions. (The other propositional connectives could be included as well, but require additional work.)

**Type checking rules.** For type checking, we gave many *rules* to define when
a program is well typed. Here are rules for variables, functions, and pairs:

```
{x : t, ...} |- x : t
```

A variable has whatever type the environment says it has.

```
env |- fun x -> e : t -> t'
if env[x -> t] |- e : t'
```

An anonymous function `fun x -> e`

has type `t -> t'`

if `e`

has type `t'`

in a
static environment extended to bind `x`

to type `t`

.

```
env |- e1 e2 : t'
if env |- e1 : t -> t'
and env |- e2 : t
```

An application `e1 e2`

has type `t'`

if `e1`

has type `t -> t'`

and `e2`

has
type `t`

.

```
env |- (e1, e2) : t1 * t2
if env |- e1 : t1
and env |- e2 : t2
```

The pair `(e1, e2)`

has type `t1 * t2`

if `e1`

has type `t1`

and `e2`

has
type `t2`

.

```
env |- fst e : t1
if env |- e : t1 * t2
env |- snd e : t2
if env |- e : t1 * t2
```

If `e`

has type `t1 * t2`

, then `fst e`

has type `t1`

, and `snd e`

has type
`t2`

.

**Proof trees.** Another way of expressing those rules would be to draw *proof
trees* that show the recursive application of rules. Here are those proof trees:

```
---------------------
{x : t, ...} |- x : t
env[x -> t1] |- e2 : t2
-----------------------------
env |- fun x -> e2 : t1 -> t2
env |- fun x -> e2 : t1 -> t2 env |- e1 : t1
---------------------------------------------------
env |- (fun x -> e2) e1 : t2
env |- e1 : t1 env |- e2 : t2
-------------------------------------
env |- (e1, e2) : t1 * t2
env |- e : t1 * t2
--------------------
env |- fst e : t1
env |- e : t1 * t2
--------------------
env |- snd e : t2
```

**Proof trees, logically.** Let’s rewrite each of those proof trees to eliminate
the programs, leaving only the types. At the same time, let’s use the
propositions-as-types correspondence to re-write the types as propositions:

```
--------
p |- p
env, p1 |- p2
-------------------
env |- p1 -> p2
env |- p1 -> p2 env |- p1
-----------------------------
env |- p2
env |- p1 env |- p2
------------------------
env |- p1 /\ p2
env |- p1 /\ p2
---------------
env |- p1
env |- p1 /\ p2
---------------
env |- p2
```

Each rule can now be read as a valid form of logical reasoning. Whenever we
write `env |- t`

, it means that “from the assumptions in `env`

, we can conclude
`p`

holds”. A rule, as usual, means that from all the premisses above the line,
the conclusion below the line holds.

**Proofs and programs.** Now consider the following proof tree, showing the
derivation of the type of a program:

```
------------------------ ------------------------
{p : a * b} |- p : a * b {p : a * b} |- p : a * b
------------------------ ------------------------
{p : a * b} |- snd p : b {p : a * b} |- fst p : a
-----------------------------------------------------------
{p : a * b} |- (snd p, fst p) : b * a
----------------------------------------------
{} |- fun p -> (snd p, fst p) : a * b -> b * a
```

That program shows that you can swap the components of a pair, thus swapping the types involved.

If we erase the program, leaving only the types, and re-write those as propositions, we get this proof tree:

```
---------------- ----------------
a /\ b |- a /\ b a /\ b |- a /\ b
---------------- -----------------
a /\ b |- b a /\ b |- a
--------------------------------------------
a /\ b |- b /\ a
------------------------
{} |- a /\ b -> b /\ a
```

And that is a valid proof tree for propositional logic. It shows that you can swap the sides of a conjunction.

What we see from those two proof trees is: **a program is a proof**. A
well-typed program corresponds to the proof of a logical proposition. It shows
how to compute with evidence, in this case transforming a proof of `a /\ b`

into
a proof of `b /\ a`

.

**Programs are proofs.** We have now created the following correspondence that
enables us to read programs as proofs:

A program

`e : t`

corresponds to a proof of the logical formula to which`t`

itself corresponds.The proof tree of

`|- t`

corresponds to the proof tree of`{} |- e : t`

.The proof rules for typing a program correspond to the rules for proving a proposition.

But that is only the second level of the Curry-Howard correspondence. It goes deeper…

## Evaluation Corresponds to Simplification¶

We will treat this part of the correspondence only briefly. Consider the following program:

```
fst (a, b)
```

That program would of course evaluate to `a`

.

Next, consider the typing derivation of that program. The variables `a`

and
`b`

must be bound to some types in the static environment for the program
to type check.

```
------------------------ -------------------------
{a : t, b : t'} |- a : t {a : t, b : t'} |- b : t'
---------------------------------------------------------
{a : t, b : t'} |- (a, b) : t * t'
----------------------------------
{a : t, b : t'} |- fst (a, b) : t
```

Erasing that proof tree to just the propositions, per the proofs-as-programs correspondence, we get this proof tree:

```
----------- -----------
t, t' |- t t, t' |- t'
----------------------------------
t, t' |- t /\ t'
----------------
t, t' |- t
```

However, there is a much simpler proof tree with the same conclusion:

```
------------
t, t' |- t
```

In other words, we don’t need the detour through proving `t /\ t'`

to prove `t`

,
if `t`

is already an assumption. We can instead just directly conclude `t`

.

Likewise, there is a simpler typing derivation corresponding to that same simpler proof:

```
------------------------
{a : t, b : t'} |- a : t
```

Note that typing derivation is for the program `a`

, which is exactly what the
bigger program `fst (a, b)`

evaluates to.

Thus evaluation of the program causes the proof tree to simplify, and the
simplified proof tree is actually (through the proofs-as programs
correspondence) a simpler proof of the same proposition. **Evaluation therefore
corresponds to proof simplification.** And that is the final level of the
Curry-Howard correspondence.

## What It All Means¶

Logic is a fundamental aspect of human inquiry. It guides us in reasoning about the world, in drawing valid inferences, in deducing what must be true vs. what must be false. Training in logic and argumentation—in various fields and disciplines—is one of the most important parts of a higher education.

The Curry-Howard correspondence shows that logic and computation are fundamentally linked in a deep and maybe even mysterious way. The basic building blocks of logic (propositions, proofs) turn out to correspond to the basic building blocks of computation (types, functional programs). Computation itself, the evaluation or simplification of expressions, turns out to correspond to simplification of proofs. The very task that computers do therefore is the same task that humans do in trying to present a proof in the best possible way.

Computation is thus intrinsically linked to reasoning. And functional programming is a fundamental part of human inquiry.

Could there *be* a better reason to study functional programming?

## Exercises¶

Solutions to most exercises are available. Fall 2022 is the first public release of these solutions. Though they have been available to Cornell students for a few years, it is inevitable that wider circulation will reveal improvements that could be made. We are happy to add or correct solutions. Please make contributions through GitHub.

**Exercise: propositions as types [★★]**

For each of the following propositions, write its corresponding type in OCaml.

`true -> p`

`p /\ (q /\ r)`

`(p \/ q) \/ r`

`false -> p`

**Exercise: programs as proofs [★★★]**

For each of the following propositions, determine its corresponding type in
OCaml, then write an OCaml `let`

definition to give a program of that type. Your
program proves that the type is *inhabited*, which means there is a value of
that type. It also proves the proposition holds.

`p /\ q -> q /\ p`

`p \/ q -> q \/ p`

**Exercise: evaluation as simplification [★★★]**

Consider the following OCaml program:

```
let f x = snd ((fun x -> x, x) (fst x))
```

What is the type of that program?

What is the proposition corresponding to that type?

How would

`f (1,2)`

evaluate in the small-step semantics?What simplified implementation of

`f`

does that evaluation suggest? (or perhaps there are several, though one is probably the simplest?)Does your simplified

`f`

still have the same type as the original? (It should.)

Your simplified `f`

and the original `f`

are both proofs of the same
proposition, but evaluation has helped you to produce a simpler proof.