= Memoizable

Memoizable allows

  require 'facets/memoizable'

The classic example is calculating the Fibonacci sequence.

  class Fibonacci
    include Memoizable

    def fib( num )
      return num if num < 2
      fib(num - 1) + fib(num - 2)
    end

    memoize :fib
  end

The first time will take a moment to calculate.

  f = Fibonacci.new
  f.fib(100)

The second time will be returned almost instantly.

  f = Fibonacci.new
  f.fib(100)

The following example makes if very clear what memoize is doing.

  class MemoExample
    include Memoizable
    attr_accessor :a
    memoize :a
  end

  m = MemoExample.new
  m.a = 10
  m.a         #=> 10
  m.a = 20
  m.a         #=> 10

Even though @a is assigned to 20, the method #a continues to return 10.

Memoizable is designed to handle a number of typically problematic aspects
of memoization implementations. First a memoizable object can be frozen.

  f = Fibonacci.new
  f.freeze
  f.fib(100)

Memoizable objects are freezable because it does not use an instance variable
to store the memoized return values. Instead it uses a global class instance
variable in +Memoizable.cache+.

However, this also means the memoization cache is not preserved when an object
is duplicated. If this behavior is desired an aditional module is provided
called `Memoizable::Copy`. When the `Memoiazble::Copy` module is included into
a class it not only adds the #memoize class methods but also includes
an #initialize_copy method, which copies the appropriate cache entries when
#dup or #clone is used. Keep this in mind. It is important to call #super if you
override the #initialize_copy method in a class that uses `Memoizable::Copy`.

  class MemoExample2
    include Memoizable::Copy
    attr_accessor :a
    memoize :a
  end

  m = MemoExample2.new
  m.a = 10
  m.a         #=> 10

  n = m.dup
  n.a = 20
  n.a         #=> 10

The `#memoize` method also accepts a few options that can be used to modify
the behvior of the memoizaiton. To memoize a method across all objects
of the same class and not just per-object, set the `:class` option to true.

  class MemoExample3
    include Memoizable
    attr_accessor :a
    memoize :a, :class=>true
  end

  m = MemoExample3.new
  m.a = 10
  m.a         #=> 10

  n = MemoExample3.new
  n.a         #=> 10

Memoization is indexed based on arguments passed to the method, as can be seen 
in the Fibonacci example. Methods can also be memoized by name only, forgoing
any indexing based on arguments passed. To do this, set the `:arguments`
option to +false+.

  class MemoExample4
    include Memoizable
    def f(n)
      n + n
    end
    memoize :f, :arguments=>false
  end

  m = MemoExample4.new
  m.f(1)
  m.f(2)      #=> 2
  m.f(3)      #=> 2

Blocks passed to a method normally have only a present vs. non-present effect
on the memoization index. In other words the return value of a method will be
cached under one key if a block is passed to it and under a different index
if not. It doesn't matter if blocks passed in actually differ, since
the only way to compare blocks is by their +object_id+ and it is very rare
for the exact same block object to be reused.

Finally, the `:freeze` option can be used to freeze the memoized results.

  class MemoExample5
    include Memoizable
    def f(n)
      (n + n).to_s
    end
    memoize :f, :freeze=>true
  end

  m = MemoExample5.new
  m.f(1).frozen?      #=> true
  m.f(2).frozen?      #=> true

