A Turing machine is a mathematical model of a mechanical machine. At its roots, the Turing machine uses a read/write head to manipulate symbols on a tape. It was invented by the computer scientist Alan Turing in 1936. Interestingly, according to the Church-Turing thesis this simple machine can do everything any other computer can do; including our contemporary computers. Though these machines are not a practical or efficient means to calculate something in the real world, they can be used to reason about computability and other properties of computer programs.
There are various different, but equivalent, Turing machine definitions. A one-tape Turing machine can be defined as a 6-tuple
- : Set of states of the machine
- : Input alphabet of the machine
- : Tape alphabet (including the blank symbol , and all symbols in the input alphabet are also in the tape alphabet, )
- : Transition function ; i.e. given a state and a symbol, the transition function will tell what the new state is, which symbol should be written and whether the head should move left or right, e.g.:
- : Initial state,
- : Set of accepting states,
The input of the Turing machine is written on the initial tape configuration. We assume that initially the first symbol on the tape is always the blank symbol , and that the following symbols are the input. We also assume that the input is followed by an infinite sequence of blank symbols (i.e., the tape is infinite).
For example, if the input is “1011”, the tape would be:
The Turing machine will start its computation in the initial state with the head at tape position 0. It will follow the transitions in the transition function, and halts when there are no more transitions it is able to take from the current head position. The input is accepted if the halting state is an accepting state. The output is the tape content after the machine has halted, but note that it is possible for machines to never halt for certain inputs.
We can represent Turing machines graphically. Let’s look at an example machine:
This machine describes the machine with:
and initial state .
If we run this machine with input “1011”, we compute . We can follow the computation by evaluating what the machine does at each step:
The input is accepted as the machine stops in . The output is the input with 0s and 1s flipped: “0100”. By analyzing the machine, we can easily see that it accepts any input that ends with a “1”, and that the output is always the input with 0s and 1s flipped.
Sometimes we want computer programs to perform computations with as input other computer programs. For example, a compiler takes source code and turns it into a program, a virus scanner can look at programs and indicate whether it believes they are viruses or not, and an interpreter takes source code and directly executes the program that is described. To be able to look at Turing machines with Turing machines, we require a means to encode such machines.
There are many different encodings possible. One such encoding is the following, where we encode Turing machines as strings of 0s and 1s. Given a machine , and by ignoring accepting states, the encoding of is :
with for a transition :
Thus, the encoding of a Turing machine is three 0s followed by the encoding of the transitions (separated by two 0s), and ends with three 0s. The example Turing machine above would be encoded to (transitions are separated by spaces for clarity):
000 101110110111011 00 1101011011011 00 11011011101011 00 11101011011011 00 111011011101011 000
A formal language is not the same as a natural language (such as English). A formal language is a set of strings made from symbols, and certain rules may apply as to which strings are in the language. Turing machines can recognize certain types of formal languages. The example machine above recognizes the language ; that is, it recognizes the language of all strings of 1s and 0s that end with a 1. Note that in recognizing languages, the output of machines is irrelevant. We only need to know whether the input is accepted or not.
There are two types of languages related to Turing machines, recursive languages and recursively enumerable languages. A recursive language is a language for which there exists a Turing machine that will accept any string that is in the language, and rejects any other string. It is important that this machine halts for every possible input. On the other hand, a recursively enumerable language is one for which we can construct a Turing machine that will enumerate all strings in the language, meaning that it will produce output:
with wi valid strings in the language. For infinite languages this enumeration operation will obviously never halt, but any given string will be reached eventually. Note that this means there does not need to exist a Turing machine that will tell you for all possible inputs whether it is or is not in the language. One is able to construct a machine that will recognize and halt for each valid string, but you cannot guarantee that the machine will halt for strings that are not in the language. One such machine can be constructed from the enumeration machine: enumerate until you find the input string, then halt and accept. Clearly, if the input string is not a valid string and the language is infinite, this machine will never halt.
The set of recursive languages is a subset of the recursively enumerable languages. Given a machine that halts for any input and accepts precisely the words of language , we can construct a machine that enumerates language . The enumerating Turing machine for language generates all possible strings and places them on the output if is accepted by .
Given an input and a description of a Turing machine (or any computer program), the halting problem is the problem of determining whether the computation will halt eventually or will run forever. Using his Turing machines, Alan Turing proved that there cannot exist a general algorithm that solves the halting problem for all pairs of computer programs and inputs; the halting problem is undecidable.
We will now prove that the halting problem is undecidable. If the halting problem would be decidable, we could make a Turing machine that, given an encoding of a Turing machine and an input for that machine, would give as output 1 if halts, and 0 if it does not. Using , we can make a machine that takes as input the code for a Turing machine and asks whether that machine halts when it looks at input ; i.e., asks whether halts. Then, we construct such that it halts precisely when does not.
Now, instead of making look at an arbitrary machine, we make look at itself: we compute . Using our definition of , we see that halts precisely when its input machine on its encoding, , does not.
However, this is a contradiction. As such, our initial assumption that the halting problem is decidable must be invalid; the halting problem is undecidable.
Gödel’s Incompleteness Theorem
Gödel’s first incompleteness theorem states that in a consistent logic system, you will not be able to prove everything that is true. A consistent logic system is one which does not contain contradictions. The first incompleteness theorem follows directly from the halting problem. To prove this, we start by assuming that we can prove all things that are true. Take two things: one being that a computer program will halt for a given input, and the other being that that computer program will not halt for that input. Given our assumption, we would be able to prove that one of these is true, and thus we would be able to decide the halting problem. However, we know that the halting problem is undecidable, so this is a contradiction. It follows that we cannot prove all things that are true, which proves Gödel’s first incompleteness theorem.