CMPS 260
Notes on Topics from Linz's Chapters 9-12

Review


Chapter 11: Recursive and RE Languages

Defn: A language is said to be recursively enumerable (RE) if there exists a TM that accepts it. (A TM accepts a string if, when given that string as input, the TM gets to a final halting state. On strings that it does not accept, it either halts in a non-final state or never halts.)

Defn: A language is said to be recursive (or decidable) if there exists a TM that accepts it and that halts on every input string. (Which is to say that, when given a string w that is a member of the language, the machine halts in a final state; but when given a string that is not a member of the language, the machine halts in a non-final state. In effect, the machine "decides" whether or not w is a member of the language.)

One useful way to look at it is that, for a language to be recursive/decidable, there must exist a membership algorithm, while to be RE requires only (the weaker condition) that there is a membership semi-algorithm (meaning one that is not guaranteed to halt when the answer to the question "Is w in L?" is NO).

Question: What does the definition of RE ("recursively enumerable") have to do with "enumeration"?
Answer: An alternative definition of RE, equivalent to the one given above is that a language is RE iff there exists a (necessarily never-ending, in the case of an infinite language) procedure that enumerates (i.e., "prints") its members. To qualify as an enumeration procedure for a language L, the procedure must have the property that, for every w ∈ L, w will be printed within a "finite amount of time" (i.e., within a finite number of steps of the computation). This does not preclude the possibility that some elements of L will be printed multiple times.

Naive Enumeration Procedure for L(M)
do for each string w ∈ Σ* 
|  if M accepts w then
|  |  print w
|  fi 
od
Consider the (naive) enumeration procedure shown to the right, intended to enumerate the members of L(M), where M is a given Turing Machine. If M halts on all inputs, the procedure will print every member of L(M) exactly once. (Note that it implicitly makes use of the fact that, for any alphabet Σ, it is possible to algorithmically iterate through "all" strings in Σ*.) This shows that, for every recursive language, there exists an enumeration procedure.

But suppose that, for some string w, M does not halt when given w as input. Then during the loop iteration in which this enumeration procedure is considering w, it will get stuck waiting, forever, for M to answer the question "Do you accept w?" It follows that this "naive" enumeration procedure, while fine for recursive languages, will not serve its intended purpose for (non-recursive) RE languages. Which means that, to show that every RE language has an enumeration procedure, we are going to have to be more clever.

An enumeration procedure that works, even when M does not halt on all inputs, is as follows:

Clever Enumeration Procedure for L(M)
k := 0
do forever 
|  k := k+1
|  do for each w ∈ Σ*k
|  |  if M accepts w in k or fewer steps then
|  |  |  print w
|  |  fi 
|  od 
od

The symbol Σ*k refers to the set containing the first k members of Σ*, with respect to some enumeration of that set. (Again, we ask that you take on faith that it is possible to algorithmically enumerate the elements of Σ*.)

Let w ∈ L(M) and let r be the smallest number satisfying the condition that both w ∈ Σ*r and M accepts w in r or fewer steps. (For every w ∈ L(M), such an r exists.) Then w will be printed during every iteration of the outer loop, starting with the r-th. This is fine, as what is required is only that every string accepted by M is eventually printed at least once.

A subtle point here is that, in this more clever enumeration procedure, M is being asked the question, "Do you accept w in k or fewer steps?", whereas in the naive procedure, M was asked, more simply, "Do you accept w?". We can justify this by reasoning that M, being a TM (or, if you prefer, a computer program), can be altered so that it keeps track of how many computational steps it has taken and so that it halts upon hitting some specified threshold. Or, alternatively, you can imagine that the enumeration procedure itself is carrying out a simulation of M's computation on w while keeping track of how many steps it has simulated. When it reaches the threshold, it stops the simulation.

Do there exist languages that are not RE?? YES!

In order to show this, it's useful to introduce the notion of a countable set.

Definition: A set is countable if it is either finite or can be placed into one-to-one correspondence with the positive integers (or natural numbers, if you prefer). The term countably infinite is sometimes used to refer to infinite sets that are countable. Technically, we say that set S is countably infinite if there exists a bijection f : ℤ+ → S.1

One way to demonstrate that a set is countable is to describe an algorithm by which to enumerate its members. Implicitly, this defines a function f as follows: f(k) = k-th element in the enumeration.

Examples of countable sets

Constructing a Non-Countable Set

Recall that, for a set S, its powerset, denoted ℘(S), is the set containing all of the subsets of S. For example, if S = {a,b,c}, then ℘(S) = { ∅, {a}, {b}, {c}, {a,b}, {a,c}, {b,c}, {a,b,c}}. If a set has n members, its powerset has 2n members, which is why some authors denote the powerset of S by 2S.

Theorem 11.1: If S is a countably infinite set, then its powerset ℘(S) is not countable.

Proof: (Cantor's Diagonalization) Assume that S = {x1, x2, x3, ... } (so that S is countable) and suppose (contrary to what is to be proved) that ℘(S) were also countable, so that there existed a one-to-one correspondence between ℘(S) and the set ℤ+ of positive integers. That would mean that the members of ℘(S) could be listed, exhaustively, as S1, S2, S3, etc., etc.

Take S' to be the set { xi∈S  |  xi ∉ Si}. Clearly, S' ⊆ S and thus (as a consequence of our assumption) there must exist k such that Sk = S'. Now consider whether or not xk ∈ Sk. We have

    xk ∈ Sk

=       < assumption Sk = S' >

    xk ∈ S'

=       < by definition of S' >

    xk ∉ Sk

What we have just proved is that, under the assumption that ℘(S) is countable, there exists a postive integer k such that the expression xk ∈ Sk is equal to its own negation! Hence, that assumption must be false. ■


Here is perhaps a slightly more concrete way of explaining why, if S is countably infinite, its powerset is not:

Each member of ℘(S) can be described by a bit string/vector whose bits are in one-to-one correspondence with ℤ+. The i-th bit in such a bit string indicates whether (1) or not (0) xi is a member of the subset of S described by that bit string. For example, the bit string representing any set containing x2, x5, x6, and x9 (and no other xi among the "first" 10 elements of S) would begin 0100110010..., with 1's in positions 2, 5, 6, and 9 and 0's in the remaining positions (among the first 10).

If ℘(S) were a countable set, there would exist a matrix T whose i-th row Ti (for all i≥1) was an infinitely-long bit string (as just described) representing Si, with its j-th bit indicating whether or not xj was a member of Si. Specifically, T would look something like this:

Ti,j = { 1if xj ∈ Si
0if xj ∉ Si
T1: 0110100110.... (describing S1)
T2: 0100001011.... (describing S2)
T3: 1101110010.... (describing S3)
T4: 0000111110.... (describing S4)
...
Defintion of TPart of T

Now define t to be the (infinite) bit string whose k-th bit is the "opposite" of the k-th bit in Tk, for all k≥1. Then t must not appear in the list of bit strings T1, T2, T3, etc., etc. (After all, by definition t differs from each Tk in at least one bit, namely the k-th bit.)

But that means that the set represented by t (which is S' = { xk ∈ S  |  xk ∉ Sk}) is not among those in the list S1, S2, ... After all, for every k, S' differs from Sk in terms of whether or not xk is a member. ■



Using this result, we can show that non-RE languages exist, simply because there are more languages than there are TM's (i.e., decision semi-algorithms) that can accept them.

Theorem 11.2: For any nonempty alphabet Σ, there exist languages over Σ that are not RE.

Proof: The languages over Σ correspond to the subsets of Σ*, but Theorem 11.1 assures us that there are an uncountable # of such languages. Meanwhile, the set of all TM's is countable, because every TM can be described by a string in {0,1}*, and that set is countable. ■

The above is a nonconstructive proof in that it fails to identify any particular language as being non-RE. (The reader may thus feel not entirely satisfied!) The next theorem remedies that by describing such a language.

Theorem 11.3: There is an RE language (over the alphabet {a}) whose complement is not RE.

Proof: (by diagonalization) Let Mi be the i-th TM. (Literally, that could mean the TM represented by the i-th string in some enumeration of Σ*, where Σ is an alphabet we have adopted for describing TM's.) Define L = { ai  |  ai ∈ L(Mi)}

First we argue that L is RE. This is so because we can devise a procedure that, upon reading ai, constructs Mi and then simulates the computation of Mi on ai. Our procedure will accept ai iff Mi does.

Now to argue that the complement of L is not RE. The complement of L is Lc = { ai  |  ai ∉ L(Mi)}.

Assume, contrary to what is to be proved, that Lc is RE. Then, by definition of RE, it is accepted by some TM, which is to say that there exists k such that L(Mk) = Lc. What happens when Mk is given ak as input? There are two possibilities, each of which leads to a contradiction:

  1. Case 1: Mk accepts ak (i.e., ak ∈ L(Mk)). But then, by definition of Lc, ak ∉ Lc, which contradicts the assumption that L(Mk) = Lc.
  2. Case 2: Mk fails to accept ak (i.e., ak ∉ L(Mk)). But then, by definition of Lc, ak ∈ Lc, which contradicts the assumption that L(Mk) = Lc.

Or, to state the argument in another way, consider the expression ak ∈ L(Mk). We have

    ak ∈ L(Mk)

=       < assumption L(Mk) = Lc >

    ak ∈ Lc

=       < by definition of Lc >

    ak ∉ L(Mk)

Our assumption that Lc is RE allowed us to prove that ak ∈ L(Mk) is equal to its own negation. Hence, that assumption must be false. ■

Theorem 11.2 demonstrates that non-RE languages exist, and Theorem 11.3 gave us a specific example. We have yet to show that there is any difference between the class of RE languages and the class of recursive languages. Theorem 11.4, in conjunction with Theorem 11.3, tells us that, indeed, the recursive languages form a proper subclass of the RE languages.

Theorem 11.4 A language L is recursive iff both it and its complement Lc are RE.

Proof: The implication from left-to-right is easy: By the definitions of recursive and RE, every recursive language is RE. It remains to show only that the complement Lc of L is also RE. But it is obvious that the recursive languages are closed under complement. (A decision algorithm for L can be transformed into a decision algorithm for Lc simply by negating the "return value".) Thus, Lc is recursive and hence RE.

To show implication from right-to-left is more difficult. Suppose that both L and its complement Lc are RE. We must show that there is a decision algorithm for L. Let M and Mc be TM's that accept L and Lc, respectively. (Such TM's must exist if L and Lc are RE.) Given a string w, to decide whether it is a member of L, give w to both M and Mc and execute them, in parallel. Because w is member of one of L or Lc, eventually one of the TM's will accept it. If w is accepted by M, answer YES. If w is accepted by Mc, answer NO. ■

A somewhat more rigorous description of a decision procedure for L, given TM's M and Mc that accept L and Lc, respectively, is as follows:

L and Lc both RE imply they are Recursive
answer := "unknown"
k := 1
do while answer = "unknown"
|  if M accepts w in k or fewer steps then
|  |  answer := "YES"
|  else if Mc accepts w in k or fewer steps then
|  |  answer := "NO"
|  else
|  |  k := k+1
|  fi
od
return answer

Theorem 11.5: There exist RE languages that are not recursive; hence, the recursive languages form a proper subset of the RE languages.

Proof: Follows immediately from Theorem 11.4, which identified an RE language whose complement is not RE, and Theorem 11.5, which tells us that any such language is not recursive.


Section 11.2: Unrestricted Grammars

The connection between automata/machines and grammars also applies to TM's in that it can be shown (see Theorems 11.6 and 11.7) that a language is RE iff it is generated by some unrestricted grammar, which is one in which the only restriction on the left-hand side of a production is that it not be λ.

Section 11.3: Context-sensitive Grammars and Languages

Section 11.4: Chomsky Hierarchy

See figures in Linz, pages 306 and 307.

Chapter 12: Limits of Algorithmic Computation

Interestingly, it is possible, in studying the limits of what computers can do, to focus on decision problems, as other kinds of problems (e.g., optimization) can be, in a sense, couched as decision problems.

Let us consider the so-called Halting Problem: Given a (string encoding a) TM (or a computer program in some language) M and an input string w, will M halt when given w as input?

Does a decision algorithm solving this problem exist? The naive (and incorrect) solution is to simulate M on input w and see what happens. If the computation halts, we answer YES. The problem with this is that, if M fails to halt, our decision "algorithm" will never provide a verdict. Now, perhaps our algorithm is "smart enough" to detect when M's computation repeats the same configuration for a second time, in which case we "know" that it is in an infinite loop and an answer of NO can be given. But not every infinite loop results in the same configuration being repeated, and so there will be cases in which our decision algorithm never gives an answer, which means that it is not really an algorithm.

The discussion above points out the difficulty of devising a solution to the Halting Problem, but it does not prove that no solution exists. What follows is such a proof.

Imagine the following Java program, where we assume that the body of the halts() method is a solution to the Halting Problem.

public class Halting1 {

   /* Prints YES if the Java program provided via the first 
   ** command-line argument, when applied to the input provided via
   ** the second command-line argument, halts.  Otherwise, prints NO.
   */
   public static void main(String[] args) {
      String javaProgram = args[0];
      String inputString = args[1];
      if (halts(javaProgram, inputString)) { 
         System.out.println("YES");
      }
      else { 
         System.out.println("NO");
      }
   }
   
   /* Returns true if the given string M is a syntactically valid
   ** Java application program that halts when given w as input 
   ** (via command line argument).  Otherwise returns false.
   */
   public static boolean halts(String M, String w) {
      ...
   }
}

Now suppose we modify the main() method of the program to get this one:

public class Halting2 {

   /* Goes into an infinite loop if the Java program provided via the 
   ** first command-line argument, when applied to the input provided via
   ** the second command-line argument, halts.  Otherwise, prints NO.
   */
   public static void main(String[] args) {
      String javaProgram = args[0];
      String inputString = args[1];
      if (halts(javaProgram, inputString)) { 
         while (true) { } // infinite loop!
      }
      else { 
         System.out.println("NO");
      }
   }
   
   /* Returns true if the given string M is a syntactically valid
   ** Java application program that halts when given w as input 
   ** (via command line argument).  Otherwise, returns false.
   */
   public static boolean halts(String M, String w) {
      ...
   }
}

We can characterize the behavior of Halting2 as follows: Given as inputs a Java program M and a string w, Halting2 halts iff M, when applied to input w, would fail to halt.

Let's modify the program again, almost imperceptibly:

public class Halting3 {

   /* Goes into an infinite loop if the Java program provided via the 
   ** first command-line argument, when applied to itself as input, halts.
   ** Otherwise, prints NO.
   */
   public static void main(String[] args) {
      String javaProgram = args[0];
      if (halts(javaProgram, javaProgram) {
         while (true) { }   // infinite loop
      }
      else { 
         System.out.println("NO");
      }
   }
   
   
   /* Returns true if the given string M is a syntactically valid
   ** Java application program that halts when given w as input 
   ** (via command line argument).  Otherwise, returns false.
   */
   public static boolean halts(String M, String w) {
      ...
   }
}

We can characterize the behavior of Halting3 as follows: Given as input a Java program M, Halting3 halts iff M, when applied to itself (i.e., its own source code), fails to halt. Let us define a Java program to be self-convergent if, when given its own source code as input, halts. Meanwhile, we define a self-divergent program to be one that is not self-convergent. Then a more concise characterization of Halting3's behavior is this:

Given as input a Java program M, Halting3 halts iff M is self-divergent.

Now we can "prove" a contradiction, which shows that our assumption (which is that the method halts() exists) is false:

    Halting3 is self-divergent

=      < Halting3 halts iff it is given as input 
         a self-divergent program's source code   >

    Halting3 halts when given its own source code as input

=      < definition of self-convergent >

    Halting3 is self-convergent

The above constitutes a proof of the undecidability of the Halting Problem. What it says is that there is no algorithm that, given a computer program M and a string w, can decide whether or not M halts if applied to w. Linz proves this in Theorem 12.1 using the Turing Machine model. Here we did it using Java programs as our model of computation.

The proof given above is "classical and of historic interest", as Linz puts it. However, there is actually a stronger theorem from which the undecidability of the Halting Problem follows as a corollary, which Linz presents as Theorem 12.2. What follows is our version of that result, again using Java as our vehicle.

Definition: With respect to a Turing Machine (i.e., computer program) M, the Single-Machine Halting Problem is this: For string w, will M halt when given w as input?

Clearly, the Single-Machine Halting Problem is "easier" than the general Halting Problem because in each instance of the former the "machine" is fixed, whereas in the latter the machine (more accurately, a description thereof) is an input. Indeed, if the general Halting Problem were decidable, it would mean that every individual Turing Machine's Single-Machine Halting Problem is decidable.

Exercise for the reader: Prove the statement above by showing how to reduce any particular instance of the Single-Machine Halting Problem to the (general) Halting Problem.

Theorem: If M is a Turing Machine (i.e., program) that accepts language L and M's Single-Machine Halting Problem is decidable, then L is recursive.
Proof: Below left is a Java program, AcceptsL, that accepts language L. Assuming the existence of a halts() method that solves the Single-Machine Halting Problem for AcceptsL (i.e., it decides whether or not AcceptsL will halt when given a specified input string), we can construct the program DecidesL below right that decides L. The existence of DecidesL means that L is recursive.

/* Java program that accepts the language L.
*/
public class AcceptsL {

   /* Prints "YES" if args[0] is a member
   ** of L; otherwise either prints "NO" or
   ** never halts.
   */
   public static void main(String[] args) {
      ...
      ...
   }
}
/* Java program that decides the language L.
*/
public class DecidesL {

   /* Prints "YES" if args[0] is a member
   ** of L; otherwise prints "NO".
   */
   public static void main(String[] args) {
      String w = args[0];
      if (halts(w)) { 
         // Print whatever AcceptsL prints when given w.
         System.out.println(AcceptsL.main(args));
      }
      else {
         System.out.println("NO");
      }
   }

   /* Returns true if, when given w as input (in the
   ** form of the first command-line argument) program 
   ** AcceptsL halts. Returns false otherwise.  (In 
   ** other words, this method solves the Single-Machine
   ** Halting Problem for AcceptsL.)
   */
   private static boolean halts(String w) {
      ...
      ...
   }
}

Using the theorem above, we can now prove Linz's Theorem 12.2:

Theorem 12.2: If the (general) Halting Problem were decidable, then every RE language would also be recursive.
Proof: Let L be any RE language, and let ML be a TM (i.e., program) that accepts it. (Of course, L's being RE implies that ML exists.) If the general Halting Problem were decidable, so would be the Single-Machine Halting Problem for ML. By the theorem proved above, then, L(ML) (i.e., L) would be recursive. ■

Corollary: The (general) Halting Problem is undecidable.
Proof: The contrapositive of Theorem 12.2 (which is equivalent to it) says that if there exists a non-recursive RE language, then the (general) Halting Problem is undecidable. But Theorem 11.5 tells us that there is such a language. ■


Proving More Problems to be Undecidable

Having proved the Halting Problem to be undecidable, the possibility for doing the same for other, related problems, exists, using the technique of reduction.

Definition: We say that problem A is reducible to problem B if the existence of an algorithm that solves B implies the existence of an algorithm that solves A.

Typically, the manner in which we show that A is reducible to B is to present an algorithmic solution for A that makes use of an (assumed-to-exist) algorithm that solves B. Often, the algorithm for B is taken to be in the form of a callable subroutine (e.g., a Java method).

Suppose that a known-to-be-undecidable problem A is reducible to problem B. Then B, too, must be undecidable! After all, the reduction itself shows that

If B is decidable, then A is decidable.

But this is equivalent to its contrapositive, which says

If A is undecidable, then B is undecidable.

In Example 12.1, Linz reduces the Halting Problem to the problem of deciding the state-entry problem: Given a TM M, one of its states, q, and an input string w, decide whether M, when presented with w as input, will ever enter state q. Thus, he concludes that the state-entry problem is undecidable.

Print GLORP Problem: As an alternative, here we will use Java programs as our model of computation to show that the analogous "Print GLORP" problem is undecidable. That is the problem of deciding, for a given Java program P and input string w, whether P, if given w as input (as, say, a command-line argument), will ever print the string "GLORP".

To show that the Print GLORP problem is undecidable, we show that the Halting Problem is reducible to it. We do so by showing how, algorithmically, any Java program M can be modified to become MGlorp having the property that, for any string w:

MGlorp prints GLORP when given w as input if and only if M halts when given w as input.

Without loss of generality, we can assume that, in M, every statement that prints anything has no other effects.2 To produce MGlorp, first remove from M all print statements. Then enclose the body of the main() method in a try-block whose lone catch-block catches all possible exceptions and does nothing in response. This ensures that the only way that MGlorp can halt is to reach the end of the main() method. Now insert the statement

System.out.println("GLORP");

following the catch-block so that it is the last statement in the main() method. This confers upon MGlorp the property that it halts iff it prints GLORP. It is also clear that MGlorp halts iff M halts. Thus, as desired, MGlorp prints GLORP iff M halts. (These statements are all with respect to all possible input strings.)

Assuming the existence of a Java method, doesItPrintGLORP(), that solves the Print GLORP problem, we can now develop an algorithm for the Halting Problem:

Given Java program M and string w:
(1) Construct MGlorp from M
(2) return the result of the call doesItPrintGLORP(MGlorp,w)

An important detail in all this is that the construction of MGlorp from M is effective (i.e., can be accomplished by an algorithm). It follows that the Print GLORP problem is undecidable.

Empty String Halting Problem: In Example 12.2, Linz shows the undecidability of the blank-tape halting problem (for Turing machines). Our analogous problem, using, once again, Java as our model of computation, is the Empty String Halting Problem. The problem is this: Given a Java program, if it were executed with the empty string having been provided to it via its command-line argument args[0], will it halt?

Following the same line of reasoning as in the previous example, given any Java program M and any string w, we can insert as the first two lines of code in the main() method of M the statements

args = new String[1];
args[0] =
w;

which has the effect of making args[] into a one-element array whose lone element is w. (Of course, in the actual program, what is shown as w here would be in the form of a String literal, such as "abbabcc".)

Call the resulting Java program Mw. Then the behavior of Mw relates to that of M as follows:

Mw, when given the empty string as input3, halts iff M halts when given input w.

Assuming the existence of a Java method haltsOnEmptyString() that solves the Empty String Halting Problem, an algorithm that solves the Halting Problem is as follows:

Given Java program M and string w:
(1) Construct Mw from M
(2) return the result of the call haltsOnEmptyString(Mw)

It follows that the Empty String Halting Problem is undecidable.

An interesting point here, which the reader may have noticed, is that the choice of the empty string was totally arbitrary. Mw, after all, totally ignores whatever input was provided to it via args[0], the first element of the main() method formal's formal parameter args[]. So we could have just as well chosen "GrisGrop" as the input string and called it the GrisGrop Halting Problem.


Example 12.3: Here Linz shows to be uncomputable the function that maps each natural number n to the maximum number of moves made by any Turing Machine having n states when applied to a blank tape. (Only TM's that halt are considered.) (This is sometimes called the Busy Beaver function.)

Here we will give the Java-oriented version of the same proof. Define f(n) to be the maximum number of statements executed by any Java program that is n lines in length (where lines are limited to at most 80 characters) when given a single command line argument that is the empty string.

Suppose that there were an algorithm that computed function f. Then an algorithm that decides the Empty String Halting Problem is as follows:

Given a Java application program M:

  1. Let n = the number of lines in M
  2. Let k = f(n) (and let ⟨k⟩ be the integer literal corresponding to k)
  3. Modify M to obtain M', as follows:
  4. Run M' (giving it the empty string as command line argument)

The output produced by M' (in the last step) will indicate whether or not M, when given the empty string as input, halts. Hence, M' is a decision algorithm solving the Empty String Halting Problem. But such a decision algorithm does not exist, as proved above. Hence, the assumption that function f is computable must be false.


Theorem 12.3': The problem of determining whether a given Java program fails to halt, no matter what input string is given to it (via args[0]) is undecidable. Call this the Never Halts problem.

Proof: We show that if the Never Halts problem were decidable, then the general Halting Problem would be decidable.

Suppose, contrary to what is to be proved, that there existed a Java method like this:

/* Given the source code of a Java program, determines whether
** or not that program will fail to halt, no matter what input 
** is provided to it (via args[0]).
*/
boolean neverHalts(String javaProgram) {
   ...
   ...
}

Then, given a Java application program M and a string w, we could decide whether or not M halts when given w as input as follows:

  1. Let ⟨w⟩ be the String literal corresponding to w. Modify M to obtain M', by inserting, as the first statement in the main() method
    if (!args[0].equals(⟨w⟩) { while (true) { } }
  2. return !neverHalts(M');

This works because, when executed, the program M' will halt iff

  1. it is given w as input, and
  2. M halts when given w as input

It follows that M' "never halts" iff M fails to halt when given w.


Footnotes

[1] A perhaps better definition of countable says that S is countable if there exists a surjection f : ℤ+ → S, as this covers both finite and countably infinite sets. (A surjective function, also known as having the "onto" property, is one such that, for every element in its range (here S), there is some (i.e., at least one) element in its domain (here, ℤ+) that maps to it.)

[2] If the arguments passed in a call to System.out.println() (or some other method that prints output) include arguments that are expressions whose evaluations could have side effects (e.g., method calls, assignment statements), the program can be modified so that those expressions are taken out of the print statement. For example, suppose that a program included the statement

System.out.println("k is " + (k=k+1) + "; item popped is " + stack.pop());

Then we could replace that statement by

k = k+1; stack.pop();

so that the program's behavior remains the same, except for it not printing anything. (We are interested only in whether or not it halts, anyway.)

[3] Actually, because Mw, in effect, erases its input string and replaces it with a hard-coded w, its behavior does not depend upon the input string given to it. Thus, this stronger statement can be made:

Mw, when given any string as input, halts iff M halts when given input w.