This post is based on a lovely article on “Deriving the Combinator”.
In a course on programming language fundamentals, our lecturer proved the Turing completeness of untyped calculus by encoding Gödel’s recursive functions into the calculus. This proof included a translation of the primitive recursion operation into a parametrized extension of the much simpler function composition operation. Sadly, the rigid nature of this translation meant that he needed new ideas to encode the -operator. These were precisely the and combinators, two magical operators that exploit the structure of the reduction rules to simulate .
In this entry, I’d like to illustrate a more natural way to represent recursion that leads quite neatly onto one (the other one) of these combinators. This way, I hope to de-mystify them a little.
Consider the following function. It’s easy to see that is primitive recursive. Consider the following (incorrect) translation: Here, is a function of three variables that reduces to the second if the first is zero, and to the third otherwise. and can be similarly understood as translations of the addition and predecessor operations in calculus.
The above translation is syntactically incorrect because is a free variable on the right hand side. It is, however, an almost mechanical translation of the function definition in -calculus. This makes it easy to generalize.
A natural fix for this syntactic error is to simply declare a parameter in the function definition. This gives One might hope that computes correctly. Sadly, this won’t work because (in the body) is designed to take in a single parameter (whereas takes 2). A creative solution suggests will now properly simulate :
Let’s take a step back and observe what we’ve accomplished. We began by realizing that to encode recursion, we needed to reference a function in its definition. While we weren’t able to do that directly, we were able to abstract that reference away as a variable that theoretically, any function could replace. But when we replaced that variable with the right function (which turned out to be the same function), we were left with an expression for the input (recursive) function.
(Notice the potential for an infinite sequence of reductions at , characteristic of Turing completeness!)
It turns out that this translation scheme is sufficient to encode all -recursive functions. Recall that the objective was to produce such that with (this is just the definition of the -operator). Consider Our discussion shows that taking captures general recursion, proving the Turing completeness of untyped calculus! Notice that
Deriving the Combinator
The first step in the translation scheme discussed so far is to regard the self-references as a parameter. In our toy example, this caused : We can easily write this interim function in calculus:
Our goal now is to construct an expression that always reduces to . The usefulness of is captured by the following reduction sequence: Using , we can trick a reference to inside . All that remains now is to solve for .
Begin by observing that we want must thus be a reducible expression. For simplicity, suppose that for some and . This gives The simplest solution here is to set and for some . Again, the simplest substitution is . This is a recursive equation of the form we’ve solved in the earlier section! Hence, set where is the solution. Write this explicitly. It appears that we’ve stumbled onto the Combinator!
To summarize, we have Such a can be written for any , and is guaranteed to reduce to : Expanding Indicating that , which makes an even simpler solution.