Lecture 10 More and More Recursive Functions

Joseph Haugh

University of New Mexico

Review

  • Q: What is a type class?

  • A: A collection of related types.

  • Q: What does the following list comprehension do?

    [ [x, y] | x <- [0..2], y <- [0..2], x /= y ]
  • A:

    [[0,1],[0,2],[1,0],[1,2],[2,0],[2,1]]

Generalizing Recursive Math Operations

Last class I posed the question: Can we generalize the pattern of defining (*) in terms of (+), (^) in terms of (*), and so on?

Yes!

Generalizing Recursive Math Operations

First let me remind you of the definition of (***) and (^^^):

(***) :: Int -> Int -> Int
m *** 0 = 0
m *** n = m + (m *** (n - 1))

(^^^) :: Int -> Int -> Int
m ^^^ 0 = 1
m ^^^ n = m *** (m ^^^ (n - 1))

Can you spot the pattern?

Exercise: Generalize the Pattern

Try to generalize this pattern by first defining a function hyper which takes three arguments:

  • x the level of recursion (level 1 is addition, level 2 is multiplication and so on)
  • m is the “base”
  • n is the “exponent”
hyper 2 2 3 = 6     = 2 * 3     = 2 + 2 + 2
hyper 3 2 3 = 8     = 2 ^ 3     = 2 * 2 * 2
hyper 4 2 3 = 16    = 2 ^!^ 3   = 2 ^ (2 ^ 2)
hyper 5 2 3 = 65536 = 2 ^!!^ 3  = 2 ^!^ (2 ^!^ 2)
hyper 6 2 3 = BIG#  = 2 ^!!!^ 3 = 2 ^!!^ (2 ^!!^ 2)

Exercise: Generalize the Pattern

Step 1: Define the type

hyper :: Int -> Integer -> Integer -> Integer

Hold up! What is the difference between Int and Integer?

Int is a fixed-precision integer type, while Integer is an arbitrary-precision integer type.

Exercise: Generalize the Pattern

Step 2: Enumerate the cases

Ask your self what are we recursing on?

hyper 1 m n = ...
hyper 2 m n = ...
hyper 3 m n = ...
hyper x m 0 = ...
hyper x m 1 = ...
hyper x m n = ...

Why so many base cases?

Exercise: Generalize the Pattern

Step 3: Define the simple cases

hyper 1 m n = m + n
hyper 2 m n = m * n
hyper 3 m n = m ^ n
hyper x m 0 = 1
hyper x m 1 = m
hyper x m n = ...

Exercise: Generalize the Pattern

Step 4: Define the other cases

hyper 1 m n = m + n
hyper 2 m n = m * n
hyper 3 m n = m ^ n
hyper x m 0 = 1
hyper x m 1 = m
hyper x m n = hyper (x - 1) m (hyper x m (n - 1))

Exercise: Generalize the Pattern

Step 4: Define the other cases

hyper 1 m n = m + n
hyper 2 m n = m * n
hyper 3 m n = m ^ n
hyper x m 0 = 1
hyper x m 1 = m
hyper x m n = hyper (x - 1) m   (hyper x m   (n - 1))
              m `hyper (x - 1)` (m `hyper x` (n - 1))
              m ***             (m ^^^       (n - 1))

Exercise: Generalize the Pattern

Step 5: Generalize and simplify

You could generalize the type to use Num, Integral and Eq type classes.

hyper :: (Num a, Integral b, Eq a, Eq b) => a -> b -> b -> b

Exercise: Generalize the Pattern

Step 6: Test the function

hyper 1 2 3 === 5
hyper 2 2 3 === 6
hyper 3 2 3 === 8
hyper 4 2 3 === 16
hyper 5 2 3 === 65536
hyper 6 2 3 === BIG#

Aside: Graham’s Number

Graham’s number is an extremely large number which can be expressed with the following function:

$$ g(n) = \left\{ \begin{array}{ll} 3 \uparrow\uparrow\uparrow\uparrow 3 & \text{if } n = 1 \\ 3 \uparrow^{g(n-1)} 3 & \text{if } n > 1, n \in \mathbb{N} \end{array} \right. $$

Graham’s number is then g(64)!

It is so large that if you filled every Planck volume with a digit in the observable universe it would not be enough to write it out!

But it can be expressed easily with the power of recursion!

Aside: Graham’s Number

How do we express it with our hyper function?

Well the single up arrow is exponentiation, the double up arrow is tetration and so on. This is called Knuth’s up-arrow notation.

Thus, 4 up arrows (the base case), is hyper 6 since hyper 3 is exponentiation.

Aside: Graham’s Number

Let’s try to define it:

graham :: Integer -> Integer
graham 1 = hyper 6 3 3
graham n = ...

Aside: Graham’s Number

What about the other case?

graham :: Integer -> Integer
graham 1 = hyper 6 3 3
graham n = hyper (graham (n - 1)) 3 3

Aside: Graham’s Number

Let’s try it:

> graham 1

And my computer exploded…

Aside: Graham’s Number

Let’s try to get a sense for just how big even the base case of this is:

$$ \begin{array}{lllllll} \footnotesize{3 \uparrow 3} & \footnotesize{=} & \footnotesize{3 * (3 * 3)} & \footnotesize{=} & \footnotesize{3 * 9} & \footnotesize{=} & \footnotesize{27} \\ \footnotesize{3 \uparrow\uparrow 3} & \footnotesize{=} & \footnotesize{3 ^ {3 ^ 3}} & \footnotesize{=} & \footnotesize{3 ^ {27}} & \footnotesize{=} & \footnotesize{7625597484987} \\ \footnotesize{3 \uparrow\uparrow\uparrow 3} & \footnotesize{=} & \footnotesize{3 \uparrow\uparrow (3 \uparrow\uparrow 3)} & \footnotesize{=} & \footnotesize{3 ^ {7625597484987}} & \footnotesize{=} & \footnotesize{\text{BIG}} \\ \footnotesize{3 \uparrow\uparrow\uparrow\uparrow 3} & \footnotesize{=} & \footnotesize{3 \uparrow\uparrow\uparrow (3 \uparrow\uparrow\uparrow 3)} & \footnotesize{=} & \footnotesize{3 \uparrow\uparrow\uparrow \text{BIG}} & \footnotesize{=} & \footnotesize{\text{REALLY BIG}} \\ \end{array} $$

And that is just the base case!!