Cite

This course has no released version yet: the text at this address may change. Cite a released version when one exists.

/courses/models-of-computation/what-is-an-algorithm
@misc{models-of-computation-what-is-an-algorithm,
  title = {{Algorithms and partial functions}},
  howpublished = {Lesson 1 of \emph{Models of computation}, Computability Theory course notes},
  url = {/courses/models-of-computation/what-is-an-algorithm},
  note = {Unreleased working version}
}

To cite a single statement, use the # link that appears next to its heading.

Lesson 1 of 13

Algorithms and partial functions

Historical motivation for a formal notion of computability, partial functions and their notation, and the coding of finite objects by integers.

30 min4 questions
Models of computation · lesson 1 of 13
  1. Algorithms and partial functions
  2. Turing machines
  3. Programming Turing machines
  4. Register machines
  5. Primitive recursive functions
  6. Coding finite sequences
  7. The Ackermann function
  8. Minimization and partial recursive functions
  9. LOOP and WHILE programs
  10. The λ-calculus
  11. Computing with λ-terms
  12. Equivalence of the models
  13. The Church–Turing thesis

1.1 The Entscheidungsproblem

Algorithms have been used since antiquity — Euclid’s algorithm for the greatest common divisor, the sieve of Eratosthenes, Gaussian elimination — without any need for a definition of the word. As long as one only wishes to show that a problem can be solved mechanically, it suffices to exhibit a procedure that everybody accepts as mechanical.

The situation changed with the foundational program of Hilbert. In 1928, Hilbert and Ackermann formulated the Entscheidungsproblem: find a procedure which, given a formula of first-order logic, decides in finitely many steps whether this formula is universally valid. Gödel’s incompleteness theorems (1931) made a positive answer increasingly implausible. But a negative answer requires proving that no algorithm solves the problem, and a statement about all algorithms can only be proved once the class of algorithms has been defined mathematically.

Several definitions were proposed between 1931 and 1936:

  • the general recursive functions of Herbrand and Gödel (1934), studied systematically by Kleene;
  • the λ-definable functions of Church and Kleene (1933–1936);
  • the Turing machines (1936), together with the closely related machines of Post (1936).

Church (1936) and Turing (1936) independently gave a negative solution to the Entscheidungsproblem. The three classes of functions were quickly proved to coincide. Turing’s analysis of what a human computer can do was decisive in convincing the community — including Gödel — that the definition was the right one.

Note that the first electronic computers were built a decade later. The notion being formalized is that of a function computable by a human following a finite set of instructions mechanically, with unlimited paper and time.

1.2 Features of an algorithm

Any proposed definition should respect the following features of the informal notion.

  1. Finite description. An algorithm is described by a finite text, fixed in advance, independent of the input.
  2. Discrete and deterministic steps. The computation proceeds in steps. At each step, the next action is completely determined by the current situation, and each action is elementary — it requires no insight.
  3. Unbounded resources. No bound is fixed in advance on the number of steps or on the amount of memory. A computation which terminates has however only performed finitely many steps, and therefore used only finitely much memory.
  4. Possible non-termination. Nothing guarantees that a computation terminates. An algorithm therefore defines a partial function, undefined on the inputs on which the computation does not terminate.

Points 3 and 4 distinguish computability theory from complexity theory: we study what can be computed in principle, not what can be computed within given resources. Point 4 is not a defect of the definitions. We will see in the course on fundamentals that it cannot be avoided: there is no programming language with a computable interpreter whose programs compute exactly the total computable functions.

1.3 Partial functions

Throughout the course, N={0,1,2,… }\nat = \{0, 1, 2, \dots\}, and x⃗\vec x abbreviates a tuple (x1,…,xk)(x_1, \dots, x_k) whose length is clear from the context.

Definition 1.1.#

A partial function f:Nk⇀Nf : \nat^k \rightharpoonup \nat is a function from a subset dom⁡f⊆Nk\dom f \subseteq \nat^k, its domain, to N\nat. We write f(x⃗) ⁣ ⁣↓f(\vec x)\converge (”ff converges on x⃗\vec x”) if x⃗∈dom⁡f\vec x \in \dom f, and f(x⃗) ⁣ ⁣↑f(\vec x)\diverge (”ff diverges on x⃗\vec x”) otherwise. We write f(x⃗) ⁣ ⁣↓=yf(\vec x)\converge = y when x⃗∈dom⁡f\vec x \in \dom f and f(x⃗)=yf(\vec x) = y. A partial function is total if dom⁡f=Nk\dom f = \nat^k.

Total functions are particular partial functions. The image of ff is im⁡f={f(x⃗):x⃗∈dom⁡f}\operatorname{im} f = \{ f(\vec x) : \vec x \in \dom f \}. The empty function, with empty domain, is denoted ∅\emptyset when no confusion is possible.

When partial functions are composed, an expression may be undefined. We adopt the following convention: an expression built from partial functions is defined only if all its subexpressions are defined. For instance, g(f(x)) ⁣ ⁣↓g(f(x))\converge requires f(x) ⁣ ⁣↓f(x)\converge and gg to converge on the value f(x)f(x). In particular, 0⋅f(x)0 \cdot f(x) is undefined when f(x) ⁣ ⁣↑f(x)\diverge: we never evaluate an expression “lazily”.

Notation 1.2.#

For two expressions e1,e2e_1, e_2 built from partial functions, we write e1≃e2e_1 \simeq e_2 (Kleene equality) to mean that either both are undefined, or both are defined and have the same value.

For instance, two partial functions f,gf, g are equal if and only if f(x⃗)≃g(x⃗)f(\vec x) \simeq g(\vec x) for all x⃗\vec x. Some authors simply write == for ≃\simeq; we will do so in later courses, when no ambiguity can arise.

1.4 Coding finite objects by integers

Algorithms manipulate finite objects: integers, but also tuples, finite sequences, strings, formulas, graphs, and programs. We will mostly study functions on N\nat. This is no restriction, because all these objects can be coded by integers in such a way that the usual operations on them become computable operations on the codes. The basic tool is a bijection between N2\nat^2 and N\nat.

Definition 1.3.#

Cantor’s pairing function is the function ⟨⋅,⋅⟩:N2→N\langle \cdot, \cdot \rangle : \nat^2 \to \nat defined by

⟨x,y⟩=(x+y)(x+y+1)2+y.\langle x, y \rangle = \frac{(x+y)(x+y+1)}{2} + y.

The pairs are enumerated diagonal by diagonal: ⟨0,0⟩=0\langle 0,0 \rangle = 0, ⟨1,0⟩=1\langle 1,0 \rangle = 1, ⟨0,1⟩=2\langle 0,1 \rangle = 2, ⟨2,0⟩=3\langle 2,0 \rangle = 3, ⟨1,1⟩=4\langle 1,1 \rangle = 4, ⟨0,2⟩=5\langle 0,2 \rangle = 5, and so on. Write T(n)=n(n+1)/2T(n) = n(n+1)/2 for the nn-th triangular number.

xy001122334401234567891011121314

The pairs (x,y)(x, y) with x+y≤4x + y \leq 4, labeled by ⟨x,y⟩\langle x, y \rangle. The enumeration follows each diagonal upwards, then jumps to the next diagonal.
Proposition 1.4.#

The function ⟨⋅,⋅⟩\langle \cdot, \cdot \rangle is a bijection from N2\nat^2 to N\nat.

Proof of Proposition 1.4.

Fix z∈Nz \in \nat. Since TT is strictly increasing with T(0)=0T(0) = 0, there is a unique nn such that T(n)≤z<T(n+1)=T(n)+n+1T(n) \leq z < T(n+1) = T(n) + n + 1. Let y=z−T(n)y = z - T(n), so that 0≤y≤n0 \leq y \leq n, and let x=n−yx = n - y. Then x+y=nx + y = n and ⟨x,y⟩=T(n)+y=z\langle x, y \rangle = T(n) + y = z, so the function is surjective.

For injectivity, suppose ⟨x,y⟩=⟨x′,y′⟩=z\langle x, y \rangle = \langle x', y' \rangle = z, and let n=x+yn = x + y, n′=x′+y′n' = x' + y'. Since 0≤y≤n0 \leq y \leq n, we have T(n)≤z≤T(n)+n<T(n+1)T(n) \leq z \leq T(n) + n < T(n+1), and similarly for n′n'. By uniqueness of nn above, n=n′n = n'; then y=z−T(n)=y′y = z - T(n) = y' and x=n−y=x′x = n - y = x'.

The inverse functions π0,π1:N→N\pi_0, \pi_1 : \nat \to \nat with π0(⟨x,y⟩)=x\pi_0(\langle x, y \rangle) = x and π1(⟨x,y⟩)=y\pi_1(\langle x, y \rangle) = y are computed by the procedure of the proof: find nn with T(n)≤z<T(n+1)T(n) \leq z < T(n+1) by trying n=0,1,…,zn = 0, 1, \dots, z, then set y=z−T(n)y = z - T(n) and x=n−yx = n - y. Only a bounded search is needed, since n≤zn \leq z. This remark will become important when we study primitive recursive functions.

Iterating the pairing function codes tuples of any fixed length: ⟨x1,x2,x3⟩=⟨x1,⟨x2,x3⟩⟩\langle x_1, x_2, x_3 \rangle = \langle x_1, \langle x_2, x_3 \rangle \rangle, and in general ⟨x1,…,xk+1⟩=⟨x1,⟨x2,…,xk+1⟩⟩\langle x_1, \dots, x_{k+1} \rangle = \langle x_1, \langle x_2, \dots, x_{k+1} \rangle \rangle. For each k≥1k \geq 1 this gives a bijection Nk→N\nat^k \to \nat. Finite sequences of arbitrary length require a little more care, since the length must be recoverable from the code; this is done in the lesson Coding finite sequences.

Strings over a finite alphabet {a1,…,am}\{a_1, \dots, a_m\} are coded by reading them as numerals in base mm with digits 1,…,m1, \dots, m (bijective numeration): the string ai1⋯aiℓa_{i_1} \cdots a_{i_\ell} is coded by ∑j=1ℓij mℓ−j\sum_{j=1}^{\ell} i_j\, m^{\ell - j}, which defines a bijection between strings and N\nat, the empty string being coded by 00. Programs, being strings, are coded the same way. With these codings, a function on finite objects is called computable if the corresponding function on codes is.

1.5 Exercises

Exercise 1.5 ★.

Show that the function N2→N\nat^2 \to \nat, (x,y)↦2x(2y+1)−1(x, y) \mapsto 2^x (2y + 1) - 1 is a bijection. Compare it with Cantor’s pairing function: which one is more economical, in the sense of the size of ⟨x,x⟩\langle x, x \rangle as a function of xx?

Solution

Every integer z≥1z \geq 1 is uniquely of the form 2x⋅m2^x \cdot m with mm odd, that is m=2y+1m = 2y + 1. So (x,y)↦2x(2y+1)(x, y) \mapsto 2^x(2y+1) is a bijection from N2\nat^2 onto N∖{0}\nat \setminus \{0\}, and subtracting 11 gives a bijection onto N\nat. On the diagonal, 2x(2x+1)−12^x(2x+1) - 1 grows exponentially, whereas ⟨x,x⟩=x(2x+1)+x\langle x, x \rangle = x(2x+1) + x is quadratic. Cantor’s pairing is a polynomial bijection; by a theorem of Fueter and Pólya, it is, up to exchanging the coordinates, the only quadratic polynomial bijection from N2\nat^2 to N\nat.

Exercise 1.6 ★.

Show that ⟨x,y⟩≥x\langle x, y \rangle \geq x and ⟨x,y⟩≥y\langle x, y \rangle \geq y for all x,yx, y, with equality only when x=y=0x = y = 0, or (x,y)=(1,0)(x, y) = (1, 0) for the first inequality.

Solution

⟨x,y⟩=T(x+y)+y\langle x, y \rangle = T(x+y) + y. Since T(n)≥nT(n) \geq n, we get ⟨x,y⟩≥x+y+y≥y\langle x, y \rangle \geq x + y + y \geq y, and ⟨x,y⟩≥T(x)≥x\langle x, y \rangle \geq T(x) \geq x. Since ⟨x,y⟩≥x+2y\langle x, y \rangle \geq x + 2y, equality ⟨x,y⟩=x\langle x, y\rangle = x forces y=0y = 0, and then T(x)=xT(x) = x, i.e. x∈{0,1}x \in \{0, 1\}. Equality ⟨x,y⟩=y\langle x, y \rangle = y forces T(x+y)=0T(x+y) = 0, i.e. x=y=0x = y = 0.

1.6 Notes and references

The Entscheidungsproblem is formulated in Hilbert and Ackermann’s textbook Grundzüge der theoretischen Logik (Springer, 1928). Its negative solution was obtained independently by Church and by Turing in 1936. The quadratic polynomial bijections N2→N\nat^2 \to \nat were determined by Fueter and Pólya (1923): up to the exchange of coordinates, Cantor’s pairing function is the only one.

  • A. Church, An unsolvable problem of elementary number theory, American Journal of Mathematics 58 (1936), 345–363.
  • A. M. Turing, On computable numbers, with an application to the Entscheidungsproblem, Proceedings of the London Mathematical Society (2) 42 (1936), 230–265.
  • R. Soare, Turing Computability: Theory and Applications, Springer, 2016, chapter 1, for a historical account.