```import Data.Map (Map)
import qualified Data.Map as Map
import Data.List(unfoldr)
import Data.Maybe(fromJust)
import System.IO.Unsafe(unsafePerformIO)
import Data.IORef(newIORef, readIORef, writeIORef)

-- First the non-recursive core of the fibonacci function
fibcore :: (Num t, Num a) => (t -> a) -> t -> a
fibcore cont = \n -> case n of
0 -> 0
1 -> 1
n -> cont (n - 1) + cont (n - 2)

-- then a pure "memoizer":
memoPure :: (Enum a, Num a) => (a -> b) -> Int -> b
memoPure f = let fs = map f [0..]
in (fs!!)

fibPure :: Int -> Integer
fibPure = memoPure \$ fibcore fibPure

-- A completely different "hand-optimized" fib
-- unfoldr :: (b -> Maybe (a, b)) -> b -> [a] 	-- Defined in Data.List
fibUnfoldr :: (Num t) => Int -> t
fibUnfoldr n = (unfoldr (\(f1,f2) -> Just (f1,(f2,f1+f2))) (0,1)) !! n

----------------------------------------------------------------
-- Start of impure part
-- memo keeps a local cache of all the previous calls
memo :: Ord a => (a -> b) -> a -> b
memo f = unsafePerformIO \$ do
history <- newIORef Map.empty
return (f' history)
where
f' history x = unsafePerformIO \$ do
tbl <- readIORef history
case Map.lookup x tbl of
Just y  -> return y
Nothing -> do
let y = f x
writeIORef history (Map.insert x y tbl)
return y

-- By memoizing the naive implementation of the Fibonacci numbers we get an
-- efficient version.
fib :: Int -> Integer
fib = memo \$ fibcore fib

-- You need to be a little careful with sharing to make sure that you get a
-- single memo structure and not one for each call. For instance, the following
-- doesn't work, since memo won't be applied until there is an argument to