Friday, October 13, 2023

The Ackermann-Péter Function in Gödel's T

The Ackermann-Péter function is defined as:

A : ℕ × ℕ A(0, n)     = n+1 
A(m+1, 0)   = A(m, 1)
A(m+1, n+1) = A(m, A(m+1, n))

Famously, it is not expressible as a primitive recursive function. However, in lecture I showed my students that it is possible to define this function in Gödel's T. That is to say, the Ackermann-Péter function is nevertheless "higher-order primitive recursive". However, I didn't have time to really explain where the definition I supplied them with came from, an issue I wanted to rectify with this blog post.

The best way to explain it is to show how the definition in Gödel's T can be derived from the original definition. To do so, let's start by writing A in a curried style:

A :A 0     n     = n+1 
A (m+1) 0     = A m 1
A (m+1) (n+1) = A m (A (m+1) n)

Note that the pair of clauses for A (m+1) look like a primitive recursive definition over the second argument. Following this idea, let's abuse notation and define a function B which is used instead of the direct call to A (m+1):

B :B m 0     = A m 1 
B m (n+1) = A m (B m n)

A :A (0)   n     = n+1 
A (m+1) n     = B m n 

This looks a bit like we are going backwards: to define B, we needed to use mutual recursion. Unfortunately, this is not a feature of Gödel's T. However, note that our definition of B only uses its m argument to pass it to A. As a result, if we pass B the function A m as an argument, we can both remove the m argument and the mutual recursion with A:

B : (ℕ  ℕ) B f 0     = f 1 
B f (n+1) = f (B f n)

A :A (0)   n     = n+1 
A (m+1) n     = B (A m) n 

These are two independent, iterative functions now! We could stop right here, and turn them from functional equations into terms in Gödel's T, except that I find the first clause of B, that B f 0 = f 1 a bit inelegant. Let's define a new function, repeat:

repeat : (ℕ  ℕ)  (ℕ  ℕ)
repeat f 0     = id
repeat f (n+1) = f ∘ (repeat f n)

The call repeat f n just defines the n-fold self-composition of the function f. I like this more than the raw definition of B, since it is a fairly mathematically natural notion. However, we can still use repeat to define B:

B : (ℕ  ℕ) B f n     = repeat f n (f 1)

Putting this all together, we can define the whole Ackermann-Péter function as:

repeat : (ℕ  ℕ)  (ℕ  ℕ)
repeat f 0 = id
repeat f (n+1) = f ∘ (repeat f n)

B : (ℕ  ℕ) B f n = repeat f n (f 1)

A :A (0)   n  = n+1 
A (m+1) n  = B (A m) n 

Since B is neither recursive, nor used more than once, it hardly makes sense as a separate definition. So we can inline it and get:

repeat : (ℕ  ℕ)  (ℕ  ℕ)
repeat f 0     = id
repeat f (n+1) = f ∘ (repeat f n)

A :A (0)   n = n+1 
A (m+1) n = repeat (A m) n ((A m) 1)

Note that the right-hand-side of the A (m+1) n clause only contains occurences of A m. Hence, we can write this whole thing as a primitive iteration.

repeat : (ℕ  ℕ)  (ℕ  ℕ)
repeat = λf:. λn:. iter(n, 0  id,
                                 S(r)  f ∘ r)

A :A = λm:. iter(m, 0  (λn:. S(n)),
                  S(r)  (λn:. repeat r n (r 1)))

And this is just the definition I gave in lecture!