From: Jonas Arvidsson Date: 2004-12-13T03:10:26+09:00 Subject: Fun with self-modifying code 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