

> {-# OPTIONS_GHC -fno-warn-missing-methods #-}

> import Prelude hiding ((^))

> infixr 8 ^

> type Natural = Integer







> fib 0 = 0

> fib 1 = 1

> fib n = fib (n-2) + fib (n-1)





fib

n

n

g

n

g

g

g



> data Machine = Done | Output Machine | Input (Natural -> Machine)





Machine

Done

Output s

s

Input f

i

f i



> run1 Done = return ()

> run1 (Output x) = print "*" >> run1 x

> run1 (Input f) = readLn >>= (run1 . f)





n

n

n

Machine

Num

fromInteger



> instance Num Machine where

> fromInteger 0 = Done

> fromInteger n = Output (fromInteger (n-1))





run1 8

a + b

b

a

b

a

b

Done

a

b

a

a + Input f

i

f i

Done

a



> a + Done = a

> a + Output b = Output (a + b)

> a + Input f = Input (\i -> a + f i)





a * b

b

Output

a

n

a * n

a

n

a * Input f

f i



> _ * Done = Done

> a * Output b = a*b + a

> a * Input f = Input (\i -> a * f i)







> w = Input fromInteger





run1 w

w * w

w * Input fromInteger

*

Input (\i -> w*i)

i

w

i

i

i

w

i+1

+

*



> (^) :: Machine -> Machine -> Machine

> a ^ Done = Output Done

> a ^ Output b = a^b * a

> a ^ Input f = Input (\i -> a ^ f i)





w ^ n

n

w ^ w

run1

+

*

^

w

f w

f

w

run1

n

w

n

w

f w

n

f n

run1 (w^w^w^w)



> run2 Done _ = 0

> run2 (Output x) as = 1 + run2 x as

> run2 (Input f) (a:as) = run2 (f a) as





run2 (w^w^w^w) [2,2..]

n

m

f w

f

f m

run2 (w^w) [4,5..]

n

fib n

n

2 ^ w

n

n

2 ^ w

fib

w ^ w

Machine

run1 (w ^ w)

w ^ w

^

w ^ 2

w * w

w * 3

w*2 + w

w*2 + 4

w * 2

w + w

w

run2 (w ^ w) [2,3..]

w ^ w

w ^ 2

w * w

w * 3

w*2 + w

w*2 + 4

w * 2

w + w

w + 5

w

6

0

w

Input

Output

run1

run2

run1

Machine

a

Done

+



> instance Show Machine

> instance Eq Machine

> instance Ord Machine



Among other things, Godel's first incompleteness theorem allows us to construct a statement in the language of Peano arithmetic that can't be proved using the axioms of Peano arithmetic. Unfortunately, this statement is a highly contrived proposition whose sole purpose is to be unprovable. People who learn of Godel's theorems often ask if there are other more natural and uncontrived mathematical statements that can't be proved from the Peano axioms.My goal in this post will be to describe one of these propositions. Not just uncontrived, but actually very useful. I only intend to tell half of the story here because I feel like there are many good treatments already out there that tell the rest. I'm just going to get to the point where I can state the unprovable proposition, and then sketch how it can be proved if you allow yourself a little Set Theory.Suppose we implement a function to compute the Fibonacci numbers like so:How do we know thatterminates for all natural number arguments? One approach is this: if we pass in the argumentit clearly never recurses more than n levels. Each time it recurses it calls itself at most twice. So it must terminate in O(2) steps (assuming that the primitive operations such as addition take constant time). We can think of this code in a kind of imperative way. It's a bit likenested loops, each loop going round up to two times.Suppose instead that we have some kind of recursive functionthat goeslevels deep but for which the number of calls ofto itself is no longer two. In fact, suppose the number of self-calls is very large. Even worse, suppose that each timeis called, it calls itself many more times than it did previously, maybe keeping track of this ever growing number through a global variable. Or instead of a global variable, maybe an evil demon decides how many timescalls itself at each stage. Can you still be sure of termination?In order to look at this question, we'll strip a computer right down to the bare minimum. It will have an input (that the evil demon could use) for natural numbers and will output only one symbol. Here's a design for such a machine:A value of typerepresents the state of the machine.means it has finished running.means output a symbol and continue in statemeans stop to input a number from the demon (or elsewhere), call it, and then continue from state. This is very much in the style discussed by apfelmus and I in recent blog posts.Here's an interpreter for one of these machines:For anywe can easily build a machine to outputstars. This is such a natural machine to want to build it seems only right to give it the name. If we want to do this then we need to makean instance ofand definefor it:Typing, say, will output 8 stars.Now given two of these machines there is a natural notion of adding them.is the machine that does everythingdoes followed by everythingdoes. (Remember, that'sthen.) To do this we need to dig intoand replace every occurrence ofin it with. That way, instead of finishing like, it leads directly into. In the case of, for each numberwe need to dig intoreplacing eachwithThere's a natural way to multiply these machines too. The idea is that inwe run machine. But each time thecommand is run, instead of printing a star it executes. You can think of this as a control structure. Ifis a natural number thenmeans running machinetimes. In the case of, instead of multiplying by a fixed natural number, we get an input from the user and multiply byinstead:We can make a machine to input a number and then output that many stars. Here it is:Try runningCan you guess what the machinedoes? Your first guess might be that it inputs two numbers and outputs as many stars as the product of the two numbers. Try it. What actually happens is that we're computing. Immediately from the definition ofwe get. In other words, the first input gives us an input, and thenis runtimes. So if we initially input, we are then asked formore inputs and after each input, the corresponding number of stars is output. Although the original expression contains just two occurrences of, we are required to enternumbers.Given the definitions ofandit seems natural to define the power operation too:The power operation corresponds to the nesting of loops. So, for example,can be thought of loops nesteddeep.Try working out whatdoes when executed withConsider the set M of all machines built using just a finite number of applications of the three operatorsandtoand the non-zero naturals. (The non-zero condition means we exclude machines like 0*w that accept an input and do nothing with it.) Any such expression can be written as, where the definition ofmakes no mention ofSuppose we useand we always enter the same natural. Then each occurrence ofacts like. So if we start with some expression in, say, then always inputtingresults instars. We could test this with, always entering 2, but it would require a lot of typing. Instead we can write another intepreter that consumes its inputs from a list rather from the user (or demon). And instead of printing stars it simply prints out the total number of stars at the end:Now you can tryand see that we (eventually) get 2If we run a machine in M there's a pattern that occurs again and again. We input a number, and then as a result we go into a loop requesting more numbers. These inputs may in turn request more inputs. Like the mythological hydra , every input we give may spawn many more requests for inputs. As the number of inputs required may depend on our previous inputs, and we may input numbers as large as we like, these machines may run for a long time. Suppose our machine terminates after requestinginputs. Then there must be some highest number that we entered. Call it. Then if the original machine was(withdefined in terms of the 3 operators and non-zero naturals), the machine must have terminated outputting no more thanstars. So if our machine terminates, we can bound how many steps it took.But do our machines always terminate? The input we give to the machine might not be bounded. If we run, say, the inputs grow and grow. If these inputs grow faster than we can chop off the heads of our hydra, we might never reach termination.Consider a program to inputand then output. It accepts an input, recurses to a depth of at most, and calls itself at most twice in each recursion. Compare with the machine. This accepts an input, recurses to a depth, calling itself exactly twice each time. So ifterminates, so does. The more complex example above where I introduced the evil demon will terminate ifdoes, as long as the demon doesn't stop inputting numbers. So if we can show in one proof that every machine of typeterminates, then there are many programs whose termination we could easily prove.Let's consider an example likewith inputs 2, 3, 4, ...We start with. Examining the definition of the operatorwe see that this proceeds by requesting an input. The first input is 2. Now we're left with. This is. Again it accepts an input. This time 3. Now we go to state. This is. Again we accept an input. This time 4. We are led to. This now outputs 4 stars and we are left withwhich is. We accept an input 5, output 5 stars and are left with. After a further input of 6, it outputs 6 stars and terminates. Or we could just runand get 15(=4+5+6) as output.The transitions are:->->->->->-> ... ->->->-> ...->-> ... ->Now for some Set Theory. Rewrite the above sequence using the transfinite ordinal ω instead of. The sequence becomes a sequence of ordinals. Any time we accept an input, the rightmost ω becomes a finite ordinal. So we have a descending sequence of ordinals. This is true whatever ordinal we start with. The execution of eitheroralways strictly decreases our ordinal, and any descending sequence of ordinals must eventually terminate . Therefore every machine in M eventually terminates.But here's the important fact: to show termination we used the ordinal ω, and this required the axiom of infinity and some Set Theory. Instead we could encode the termination question, via Godel numbering, as a proposition of Peano arithmetic. If we do this, then we hit against an amazing fact. It can't be proved using the axioms of Peano arithmetic. So we have here a useful fact, not a contrived self-referential one, that can't be proved with Peano arithmetic.A few years back, Jim Apple made a post about constructing (some) countable ordinals in Haskell . His construction nicely reflects the definitions a set theorist might make, but the code doesn't actually do anything. Later I learned from Hyland and Power how you can interpret algebraic structures as computational effects. apfelmus illustrates nicely how an abstract datatype can be made to do things with the help of an interpreter. Roughly speaking, doing this is what is known as operational semantics . So I thought, why not apply this approach to the algebraic rules for defining and combining ordinals. The result are the interpretersandabove.gives an example of a Hydra game . In fact, its precisely the hydra game described in this paper because it always chops off the rightmost head. The Kirby-Paris theorem tells us we can't prove this game terminates using just the Peano axioms. A web search on Goodstein's theorem will reveal many great articles with the details.A well-ordered quantity that you can keep decreasing as a program runs, and that can be used to prove termination, is an example of a loop variant . Loop variants are often natural numbers but the above shows that transfinite ordinals make fine loop variants. But in the interest of being fair and balanced, here 's a dissenting view. The author has a point. If you are forced to use transfinite ordinals to show your program terminates, the age of the universe will probably be but the briefest flicker compared to your program's execution. On the other hand, if you don't want an actual bound on the execution time, ordinals can provide very short proofs of termination for useful programs.(By the way, this article is a sequel to my own article .)Algebraic structures give rise to monads. Can you see how to generalise the definition ofto make it a monad? If you pick the right definition then the substitution offorin the definition ofshould give you a particularly simple definition of ordinal addition. (See this for a hint on how substitution works in monads.)