Definitions and Theorems

advertisement
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
Download