Hi,

In the "Class and Module Definitions" section of Chapter 19
in Pickaxe 1, there's code for a method called ExampleDate.once.
I've tried to generalize it in order to get return value
caching that works for any method - provided that the cached
method follows some rules:
- It must return a value based only on the values of the
   arguments it receives.
- It can not have any side effects (such as changing some state
   or doing any I/O operations).

You call cacheable or cacheable_singleton in the class where the
methods to cache are defined, a bit like private or attr_reader
and such. I put the "cacheable..." methods in Module at first,
but then they wouldn't work with methods on the outermost level.

What do you think? Is this technique fool-proof? Can it be
optimized? Any other comments?

I'm including an example of one case where caching works
beautifully. It's an implementation of the Fibonacci series using
recursion.

--Jonas

#---------------------- cacheable.rb ------------------------------
def cacheable_def(*ids)
     ids.each { |id|
         orig_method = cache = "__#{id.to_i}__"
         yield <<-end_code
             alias #{orig_method} #{id}

             def #{id}(*args, &block)
                 def #{id}(*args, &block)
                     key = args.inspect + "\#{block}"
                     return @#{cache}[key] if @#{cache}.has_key? key
                     @#{cache}[key] = #{orig_method}(*args, &block)
                 end
                 @#{cache} = {}
                 self.#{id}(*args, &block)
             end

             END { puts "Cached \#{@#{cache}.size} values for #{id}" } if 
$DEBUG
         end_code
     }
end

def cacheable(*ids)
     cacheable_def(*ids) { |code| class_eval code }
end

def cacheable_singleton(*ids)
     cacheable_def(*ids) { |code| instance_eval code }
end

#------------------------- fib.rb ------------------------------------
require 'benchmark'
require 'cacheable'

def fib(n)
     n < 2 ? n : fib(n - 2) + fib(n - 1)
end

max = 30
results = []

Benchmark.bm(7) do |x|
     x.report('normal:') { results << fib(max) }
     cacheable_singleton :fib
     x.report('cached:') { results << fib(max) }
end

p results