Decidability and Undecidability

“I was peeling a red apple from the garden when I suddenly understood that life would only ever give me a series of wonderfully insoluble problems. With that thought, an ocean of profound peace entered my heart.”
—Christian Bobin

Given an input string, a Turing Machine can accept, reject, or run forever without providing an answer. This third possibililty means we have to be more careful about our definitions.

The Language of a Turing Machine

Definition

A Turing Machine MM accepts a string ww if MM halts-and-accepts when started on a tape containing the string ww.

A Turing Machine MM rejects a string ww if MM halts-and-rejects when started on a tape containing the string ww.

The language of a Turing Machine MM, denoted L(M)L(M), is the set of strings that MM accepts.

So if a string ww is not in L(M)L(M), there are be two potential reasons: either MM eventually rejects ww, or MM runs forever without specifically accepting or rejecting ww.

Decidability and Undecidability

Definition

A language is decidable if it is the language of some terminating TM.

So the following are all equivalent ways of saying that a language LL is decidable:

  • LL is the language of some TM MM that always terminates.
  • There exists a TM MM such that MM accepts ww iff wLw\in L and MM rejects ww iff w∉Lw\not\in L.
  • There exists a TM MM such that MM accepts every string in LL, and rejects any string not in LL.
  • There exists a TM MM that always (eventually) tells us whether or not its input is in LL.

Definition

A language is undecidable if it is not decidable.

The family of all decidable languages is closed under union, intersection, and complement. We prove the last of these:

Theorem

If LL is a decidable language, then LcL^c is decidable.

Proof:
Assume LL is a decidable language.

Then there is a TM MM which decides LL (i.e., accepts every string in LL and rejects every string not in LL).

Let MM' be a copy of MM except we swap transitions to the accept and reject states.

Then MM' accepts every string not in LL (i.e., every string in LcL^c) and rejects every string in LL (i.e., every string not in LcL^c).

Thus MM' decides LcL^c, and LcL^c is decidable.

Decidability and Undecidability of Acceptance

We now turn to our first concrete examples of an undecidable language, ATMA_\mathrm{TM}. We start with some simpler (decidable) languages for contrast.

Definition

The notation \langle \cdots \rangle means ”\cdots encoded as a string.”

For example, if MM is a Turing Machine then M\langle M\rangle is the full specification of MM as a finite string.

Similarly if DD is a DFA and ww is a string in the alphabet of that DFA, then D,w\langle D,w\rangle is a string that specifies both a description of DD (its states, transitions, etc.) and a the contents of the string ww.

Example

The following languages are decidable:

  1. ADFA={ D,w   D a DFA, wL(D) }A_\mathrm{DFA} = \{\ \langle D, w\rangle\ \ |\ D \mathrm{~a~DFA},\ w\in L(D)\ \}
  2. ANFA={ N,w   Nan NFA, wL(N) }A_\mathrm{NFA} = \{\ \langle N, w\rangle\ \ |\ N \mathrm{an~NFA},\ w\in L(N)\ \}
  3. ARE={ R,w   R a regexp, wL(R)  }A_\mathrm{RE} = \{\ \langle R, w\rangle\ \ |\ R \mathrm{~a~regexp},\ w\in L(R)\ \ \}
  4. ACFG={ G,w   G a CFG, wL(G) }A_\mathrm{CFG} = \{\ \langle G, w\rangle\ \ |\ G \mathrm{~a~CFG},\ w\in L(G)\ \}

Proof:

  1. Given DD and ww, follow the appropriate transitions corresponding to ww in the automaton DD, and check whether the final state is accepting.
  2. Use the subset construction to turn NN into an equivalent DFA, and check whether ww is accepted by that DFA. (Or, run ww through the NFA, and keep track at each step what possible states NN could be in.)
  3. Convert RR into an NFA, and then check whether ww is accepted by that NFA.
  4. Use a parsing algorithm like the CYK Algorithm to determine whether ww can be produced by grammar GG.

However, when we move from DFAs and CFGs to Turing Machines, it is undecidable whether a machine will accept a given string.

Example

The language

ATM={ M,w  M a TM, wL(M) }A_\mathrm{TM} = \{\ \langle M, w\rangle\ |\ M \mathrm{~a~TM},\ w\in L(M)\ \}

is undecidable.

Proof (by contradiction):

Suppose ATMA_\mathrm{TM} were decidable.

Then there is a Turing Machine (a piece of code) decide_Atm that took as input M,w\langle M,w\rangle (a string containing both the specification/code for a machine MM and a specific input string ww for the machine MM) and always told us whether MM would accept ww or not.

Note: decide_Atm can’t check this by running MM on ww, because that might never terminate. decide_Atm would presumably study the definition/code of MM and the input string ww and do some arbitrarily complex calculations to predict the behavior of MM on that input.

We could then construct a “bigger” Turing Machine C to perform a calculation according to the following pseudocode:

def Cantor(x):
      return not (decide_Atm(x, x))

The idea is that we would run Cantor on the description M\langle M\rangle of some a Turing Machine; Cantor would use the procedure decide_Atm to ask whether Turing Machine MM would accept the string M\langle M\rangle or not (i.e., whether MM’s source code is a string that machine MM would accept). Whatever that answer is, C then returns the opposite.

Since we have assumed decide_Atm can be implemented and always returns an answer, we can also implement C and be sure it always returns an answer as well.

For example, if MM was a Turing Machine that checked whether its input was an even-length palindrome, then Cantor(M)\mathtt{Cantor}(\langle M\rangle) would probably return True (accept):

  • The code of a TM for checking palindromes is probably not a palindrome.
  • So, MM would probably not accept its own source code M\langle M\rangle.
  • decide_Atm(M,M\langle M\rangle, \langle M\rangle) would predict that and return False (reject).
  • Cantor negates this, and returns True (accepts).

So far, so good. The problem is this: we’ve implemented Cantor as a piece of code (a Turing Machine). What happens if instead of applying Cantor to the code for a palindrome-checker, we apply Cantor to its own source code? According to the definition

def Cantor(x):
      return not (decide_Atm(x, x))

the call Cantor(Cantor\langle\mathtt{Cantor}\rangle) would ask decide_ATM whether the machine Cantor accepts when given its own source code as input, and then returns the opposite answer. This is a problem, because

  1. Cantor can’t accept its own source code. If it did, decide_Atm would predict this fact. Then Cantor would negate this answer and return False (rejecting its own source code).
  2. Cantor can’t reject its own source code. If it did, decide_Atm would predict that fact. Then Cantor would negate this answer and return True (accepting its own source code).
  3. Cantor can’t compute forever; it just calls decide_Atm (which always returns an answer), and takes one more step to negate that answer.

We have reached a contradition: Cantor(Cantor\langle\mathtt{Cantor}\rangle) cannot accept, cannot reject, and cannot run forever.

Therefore, our assumption (that we could implement a decider for ATMA_\mathrm{TM}) must be wrong, and ATMA_\mathrm{TM} is not decidable.

One might worry that there’s something fishy about applying a program to its own source code, but this happens all the time in the real world, e.g.,

  • The Unix utility wc can be used to count the number of lines in a text file. This program is implemented in C; if we wanted to whether this code is long and complicated, it would be perfectly reasonably to run wc on its own code w.c to see how many lines are in the source code.
  • The Visual Studio Code editor is written as a collection of JavaScript and TypeScript files. If we wanted to add a new feature to VSCode, we could edit the JavaScript and TypeScript files using VSCode to add the functionality. When we restart the editor, the new feature will be available.
  • The clang++ C++ compiler is written in C++. To add a new feature to the compiler (e.g., when a new standard for C++ adds extra features to the C++ languages), we edit the C++ source, compile that C++ source with the old clang++ compiler, and produce a new clang++ executable having the new feature.

So the idea of applying a program to its own source code is not inherently contradictory.

What Does This Mean?

Saying that ATMA_\mathrm{TM} is undecidable means that there is no Turing Machine (and hence no code and no algorithm) that can take an arbitrary TM and an arbitrary input, and correctly predict (in finite time) whether this TM will accept that input.

It does not mean we can never answer the question! It’s quite easy to build a TM that never accepts. If we ask whether this particular machine accepts ϵ\epsilon, we have no problem deciding the answer is “no.”

Conversely, we might be able to look at a more interesting Turing Machine and realize it accepts all (and only) even-length palindromes.

But there is no algorithm for deciding acceptance that works in all cases.