Definitions and Theorems Enumerating and Executing Programs Pick a programming language L. Examples: L = Java, C, Turing Machines, etc. By Church's Thesis, the choice of L doesn't matter as long as it is complete. P is a valid L-program if 1. P is a string over TOKENS(L) = the alphabet of legal L-symbols (names, numbers, operators, punctuation, etc.) 2. P contains no syntax errors. Note that 2 can be checked with a parser for L: parse(P) = if (P is valid) true else false We can devise an algorithm that enumerates all valid programs of L: prog(i) = Pi = the ith valid L program An interpreter or universal/virtual machine for L is a program called VM that can execute L programs: VM(i, x) = the result of executing Pi on input x VM(i, x, y) = the result of executing Pi on inputs x and y etc. Notation: Pi(x) = y if VM(i, x) = y Pi(x, y) = z if VM(i, x, y) = z etc. Note: VM = Pu for some u. I.e., VM(i, x, y, z) = Pu(i, x, y, z) = Pi(x, y, z) This follows because a VM can be implemented in any language L. Definitions A function f: NAT -> NAT is partial recursive if for some i, f(x) = Pi(x). We say that Pi implements or computes f. Of course f may be undefined for some inputs. In other words, f may be a partial function. If f(x) is undefined, then Pi(x) does not halt. Instead it may enter an infinite loop or go off on a never-ending search. If Pi(x) is defined for all x NAT, then Pi implements a total recursive function. (We use the terms "recursive" and "total recursive" interchangeably.) A NAT is recursive if its membership function is recursive. A membership function for A is a function memA: NAT -> BOOLEAN such that memA(x) = if (x A) true else false A is recursively enumerable if A = or if A can be enumerated by a recursive function. In other words, there is a recursive function f: NAT->A such that for every x A there is an n NAT such that f(n) = x. In still other words, A = Wi = the set of all outputs of Pi for some i. It's convenient to define the following subsets of P(NAT): RE = all recursively enumerable subsets of NAT REC = all recursive subsets of NAT co-RE = complements of sets in RE Notes: Although we have defined partial recursive functions to be of type NAT->NAT, our definitions easily extend to NATk->NAT. This follows because a pair of naturals (n, m) and be encoded as a single natural <n, m> = 2n*3m. So Pi(n, m) can be interpreted as Pi(<n, m>). Our definitions also extend to functions of type A*->A* where A CHAR. This follows because each u A* can also be encoded as a natural number <u>. Other terms: Recursive functions are also called computable or effectively computable functions. Recursive sets are also called decidable sets. If we don't assume Church's Thesis, then recursive becomes L-recursive, where L is the chosen programming language. Of course the existence of cross-compilers implies that C-recursive = Java-recursive = TMrecursive = etc. Theorems Theorem: The following functions are recursive: inc(x) = x + 1 dec(x) = if (x == 0) 0 else x – 1 Proof: Clearly inc and dec can be implemented in Java if x is of type int. However, int is not the same as NAT. (Why?) To prove this we need to define: class BigNat { ... } // multiple precision natural numbers Each instance of BigNat contains a list of integers representing mega-digits: class BigNat { private ArrayList<Integer> digits; // ... } We only allow non-negative integers to be digits. For example, if x.digits = (a, b, c), then x = a * m2 + b * m1 + c * m0 where m = 1 + Integer.MAX_VALUE (= 231) Complete the proof by implementing inc and dec as BigNat methods. Corollary: +, *, ^, - are all recursive. Proof: Complete the proof by implementing add, mul, exp, and sub as BigNat methods. Hint: use recursion and inc and dec from above. Theorem: There are 0 partial recursive functions. Proof: This follows because programs are strings and there are only 0 strings. QED Corollary: |RE| = |co-RE| = |REC| = 0 Corollary: There are functions which are not partial recursive. Proof: Because 0 < |NAT->NAT| QED Corollary: There are sets which are not recursively enumerable. Proof: Because 0 < |P(NAT)| QED Theorem: If A is recursive, then A is recursively enumerable (REC RE). Proof: A is recursive means its membership function is recursive. To show A is recursively enumerable, we must find an algorithm that enumerates A. Our proof consists of a Java function that takes two parameters: an integer n and a membership function for A. The output is the nth member of A: import java.util.function.*; public class RecIsRE { public static Integer enum (Integer n, Predicate<Integer> mem) { Integer next = (n==0)? 0: enum(n - 1, mem); do {next++;} while(!mem.test(next)); return next; } // testing: public static Boolean isEven(Integer n) { if (n % 2 == 0) return true; else return false; } public static void main(String args[]) { for(int i = 0; i < 20; i++) { System.out.println("enum (" + i + ") = " + enum(i, RecIsRE::isEven)); } } } Output enum(0) = 0 enum(1) = 2 enum(2) = 4 enum(3) = 6 enum(4) = 8 enum(5) = 10 enum(6) = 12 enum(7) = 14 enum(8) = 16 enum(9) = 18 enum(10) = 20 enum(11) = 22 enum(12) = 24 enum(13) = 26 enum(14) = 28 enum(15) = 30 enum(16) = 32 enum(17) = 34 enum(18) = 36 enum(19) = 38 QED Our function relies heavily on Java 8 lambdas. Theorem (Halting Problem): The function halt(x, y) = if (Px(y) hlts) true else false Is partial recursive but NOT total recursive. Proof: Assume halt(x, y) is total recursive and define: weird(x) = if (!halt(x, x)) 0 else while(true) no-op; In other words, weird(x) = 0 if Px(x) doesn't halt and goes into an infinite loop if it does. If halt is recursive, then weird is partial recursive and therefore weird(x) = Pw(x) for some w. But weird(w) = Pw(w) = 0 if Pw(w) doesn't halt and if Pw(w) doesn't halt, then Pw(w) = 0. On the other hand halt(x, y) can simply call VM(x, y) as a subroutine and output true if it halts. If, however, VM(x, y) doesn't halt, then neither will our halt function. In other words: halt(x, y) = if (Px(y) halts) true else undefined QED Theorem: There are recursively enumerable sets that aren't recursive. Proof (by diagonalization) Let K = { i | Pi(i) is halts}. K is recursively enumerable (proof?). If K is recursive, then its membership function is recursive: memK(x) = if (x K) true else false But now we can define: weird(x) = if (!memK(x)) 0 else while(true) no-op; So weird(x) = Pw(x) for some w. If w K (i.e. memK(w) = true,) then Pw(w) = weird(w) halts (= 0) . But weird(w) halts means memK(w) = false which means w K. If w K (i.e. memK(w) = false,) then weird(k) is undefined (loops forever) and so memK(w) = true which means w K. QED. Theorem: A is recursively enumerable if it can be recognized by a partially recursive function. (I.e., if A = domain(Pi) for some i.) Proof (Dovetail): A is RE means A = range(Pj) for some j. In other words: A = {Pj(0), Pj(1), Pj(2), ... } = { x | x = Pj(n) for some n} We must find an algorithm, Pi, such that A = {x | Pi(x) halts}. Here's our algorithm: public class Recognizer { public static Integer recognizer(Integer x, BiFunction<Integer, Integer, Integer> enumerator) { boolean done = false; int maxSteps = 1; while(!done) { for(int c = 0; c < maxSteps; c++) { for(int steps = 1; steps < maxSteps; steps++) { done = enumerator.apply(c, steps) != null; } } maxSteps++; } return 0; } } The idea is that enumerator(n, m) is a variation of the enumerator for A. This version runs Pj(n) for m steps. If Pj(n) halts, then it returns the nth element of A, just like Pj(n) does, if it doesn't halt after n steps, then null is returned. Later we will call enumerator(n, m + 1). Here's the pattern of calls (called the dovetail pattern). Notice that even in Pj(0) is undefined (runs forever), we don't do the whole thing at once, we do bits and pieces of it while searching for outputs of the other computations in between. Calculation # Steps 1 2 3 4 Pj(0) 1 3 4 10 Pj(1) 2 5 9 12 Pj(2) 6 8 13 19 Pj(3) 7 14 18 25 Pj(4) 15 17 26 32 Pj(5) 16 27 31 Pj(6) 28 30 Pj(7) 29 … 5 6 7 … 11 21 22 etc 20 23 35 24 34 33 QED Theorem: If A and B are recursive, then so are A B, A B, A – B, and A X B. Proof: Given recursive membership functions for A and B, we create a membership function for A B. The rest are left as exercises: package theorems; import java.util.function.*; public class ClosureProps { public static Predicate<Integer> union(Predicate<Integer> memA, Predicate<Integer> memB) { return (n) -> memA.test(n) || memB.test(n); } // tests public static boolean isSmall(Integer n) { return n < 10; } public static boolean divBy5(Integer n) { return n % 5 == 0; } public static void main(String args[]) { Predicate<Integer> mem = union(ClosureProps::isSmall, ClosureProps::divBy5); for(int i = 0; i < 21; i++) { System.out.println("mem(" + i + ") = " + mem.test(i)); } } } Output: mem(0) = true mem(1) = true mem(2) = true mem(3) = true mem(4) = true mem(5) = true mem(6) = true mem(7) = true mem(8) = true mem(9) = true mem(10) = true mem(11) = false mem(12) = false mem(13) = false mem(14) = false mem(15) = true mem(16) = false mem(17) = false mem(18) = false mem(19) = false mem(20) = true QED Theorem: If A and B are recursively enumerable, then so are A B, A B and A X B. Proof: Exercise. Theorem: REC = RE co-RE Proof: We know REC RE since REC is closed under complements, REC co-RE, therefore REC RE co-RE. Assume A RE and B = NAT - A RE. Here's how we define a recursive membership function for A: public class CoREintersectRE { public static boolean member(Integer x, Function<Integer, Integer> enumA, Function<Integer, Integer> enumB) { int i = 0; while(true) { if (x == enumA.apply(i)) return true; if (x == enumB.apply(i)) return false; i++; } } } Why does this halt for all inputs? QED Corollary: There is a recursively enumerable set A such that NAT – A is not recursively enumerable. The Big Picture Here's the big picture. It's not drawn "to scale" however, since |RE| = |co-RE| = |REC| = 0 < |P(NAT)|. P(NAT) RE REC co-RE