Memoization

A typical “Hello World” program in functional programming languages is a recursive function that computes the Fibonacci sequence. In Ruby, the trivial implementation looks like this:

def fib(n)
  return n if (0..1).include? n
  fib(n-1) + fib(n-2)
end

However, you’ll feel the pain that is relying on deep recursion in Ruby if you compute even modest values of n. On my machine, fib(30) computes within a few seconds, but I’m too impatient to even give you a time for fib(40). However, there is a special characteristic of functions like this that makes it possible to speed them up drastically.

In mathematics, a function is said to be well defined if it consistently maps its input to exactly one output. This is obviously true for fib(n), as fib(6) will always return 8, no matter how many times you compute it. This sort of function is distinct from one that is not well defined, such as the following:

def mystery(n)
  n + rand(1000)
end

If we run this code a few times with the same n, we see there isn’t a unique relationship between its input and output:

>> mystery(6)
=> 928
>> mystery(6)
=> 671
>> mystery(6)
=> 843

When we have a function like this, there isn’t much we can assume about it. However, well-defined functions such as fib(n) can get a massive performance boost almost for free. Can you guess how?

If your mind wandered to tail-call optimization or rewriting the function iteratively, you’re thinking too hard. However, the idea of reducing the amount of recursive calls is on track. ...

Get Ruby Best Practices now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.