module Main where

main = print (fib' 100)
        -- This will time out unless memoing works properly

data Nat = Z | S Nat
           deriving (Show, Eq)

memo f = g
  where
    fz = f Z
    fs = memo (f . S)
    g  Z    = fz
    g (S n) = fs n
        -- It is a BAD BUG to inline 'fs' inside g
        -- and that happened in 6.4.1, resulting in exponential behaviour

-- memo f = g (f Z) (memo (f . S))
--        = g (f Z) (g (f (S Z)) (memo (f . S . S)))
--        = g (f Z) (g (f (S Z)) (g (f (S (S Z))) (memo (f . S . S . S))))

fib' :: Nat -> Integer
fib'             =  memo fib
  where
  fib Z          =  0
  fib (S Z)      =  1
  fib (S (S n))  =  fib' (S n) + fib' n

instance Num Nat where
  fromInteger 0        =  Z
  fromInteger n        =  S (fromInteger (n - 1))
  Z + n                =  n
  S m + n              =  S (m + n)
  Z * n                =  Z
  S m * n              =  (m * n) + n
  Z - n                =  Z
  S m - Z              =  S m
  S m - S n            =  m - n

instance Enum Nat where
  succ                 =  S
  pred Z               =  Z
  pred (S n)           =  n
  toEnum               =  fromInteger . toInteger
  fromEnum Z           =  0
  fromEnum (S n)       =  fromEnum n + 1

