Functional Programming and Natural Deduction
Finally, here is the "big reveal" about natural deduction. The proofs that we constructed were really just programs in a close relative of ReasonML! Here is a table explaining the analogy:
| Functional Programming | Natural Deduction |
|---|---|
function type A => B | implication |
function value (x: A) => { ... body of type B ... } | Introduction |
application f(a) | Elimination, from and |
tuple type (A, B) | conjunction |
tuple value (a, b) | Introduction from and |
projections fst, snd | Elimination 1 and 2 |
variant type Left(A) | Right(B) | disjunction |
constructors Left, Right | Introduction 1 and 2 |
| switch statement | Elimination |
unit type |
The most complicated comparison here is viewing the switch statement as doing elimination. Consider a proof such as
Here is an analogous ReasonML function:
type disj('a, 'b) = Left('a) | Right('b);
let orCommutative: disj('a, 'b) => disj('b, 'a) = (l1: disj('a, 'b)) => {
let l6: disj('b, 'a) = switch (l1) {
| Left(l2: 'a) => {
let l3: disj('b, 'a) = Right(l2);
l3
}
| Right(l4: 'b) => {
let l5: disj('b, 'a) = Left(l4);
l5
}
};
l6
};
orCommutative(Left(42));
orCommutative(Right("hello"));
More idiomatically, taking advantage of type inference and not using so many
let statements to label each "line" of the proof, we can write this as:
let orCommutative = a_or_b => {
switch (a_or_b) {
| Left(a) => Right(a)
| Right(b) => Left(b)
}
};
orCommutative(Left(42));
orCommutative(Right("hello"));
One of the very powerful aspects of this analogy between typed functional programming and logical proofs is that, for those parts of a program that are just doing the "administrative" work of shuffling around pieces of data structures in a generic way, there is just one straightforward way to put the pieces together that will satisfy the type-checker. Writing this kind of program is akin to proving an equivalence in logic, and there is a strong hope that this sort of code could be generated automatically, or at least with significant machine assistance, leaving programmers to work on the more interesting parts of the problem.
Missing from this analogy is how to treat negation and contradiction ().
The simplest approach in ReasonML is probably to treat negation as
equivalent to the implication . We do not have a type that
corresponds to , because there are not supposed to be any values of that
type (since they would correspond to proofs of a contradiction!). However,
we can extend our analogy so that reaching a contradiction is like throwing
an exception to abort the program. In ReasonML, the expression raise Exit
may be used where any type of value is expected, and if it is evaluated then
the program will abort (unless we have an exception handler in place…).
This corresponds to ex falso quodlibet, the elimination rule: if we
reach a contradiction, we can get a proof of any proposition, i.e., a result
of any type. If negation is an implication of , then the analogue to
in ReasonML would be a function that takes a parameter of type A
and throws an exception—if it is truly the case that there is no value
of type A (that is, no proof of , which is what we would hope if
is true), then this function can never be called.1
Exercises
-
Based on the
curryanduncurryfunctions, give a natural deduction proof of the arguments and its converse . -
We have observed that modus ponens, the elimination rule, corresponds to function application. What operation on functions corresponds to the Law of Syllogism ()?
Answer
An operation that takes functions and is the composition .
-
Prove the logical equivalence . Give the analogous ReasonML functions that show the 1-1 correspondence between the types
disj('a, 'b) => 'cand('a => 'c, 'b => 'c).Answer
let forward = f => {
(a => f(Left(a)), b => f(Right(b)))
};
let reverse = p => {
let (a_to_c, b_to_c) = p;
a_or_b => {
switch (a_or_b) {
| Left(a) => a_to_c(a)
| Right(b) => b_to_c(b)
}
}
};