Wednesday, March 28, 2007

Calculate once Cache forever

In the pickaxe book there is a section which talks about the use case of a "once" directive. So you have a method with complex calculations and you want to be able to cache the result.
One "clunky" technique mentioned there is to store the result in an instance variable and check it before doing the calculation.

def as_string
unless @string
# complex calculation
@string = result
end
@string
end

The book then introduces Tadayoshi Funaba's once directive implementation.

def once(*ids)
for id in ids
module_eval <<-"end;"
alias_method :__#{id.to_i}__, :#{id.to_s}
def #{id.to_s}(*args, &block) (@__#{id.to_i}__ ||=
[__#{id.to_i}__(*args, &amp;block)])[0]
end
end;
end
end

(For details please look at Funaba San's once )

There is a related technique that I wanted to talk about. This is slightly different in semantics as it rewrites the method itself and though cannot be a substitute for the once directive but could be used in this or similar situations.
Let's look at the code first.

class Test
def calc
# complex calculation
@result = "result"
puts "Whew! Here is the result"
if @result
def self.calc
puts "No sweat!"
@result
end
end
@result
end
end

What is happening here is that when the class is evaluated the outer method calc() is defined, but when the method calc() is executed, conditionally the singleton method calc() is defined on the object. The "self" here would refer to the object of the class Test in whose context the outer method calc() is executed. The end result is that we get a singleton method.

irb(main):077:0> t1 = Test.new
=> #
irb(main):078:0> t1.calc
Whew! Here is the result
=> "result"
irb(main):079:0> t1.calc
No sweat!
=> "result"

irb(main):084:0> t2 = Test.new
=> #
irb(main):085:0> t2.calc
Whew! Here is the result
=> "result"
irb(main):086:0> t2.calc
No sweat!
=> "result"

This can be used to conditionally re-define the behavior of a class on an object by object basis and so has a more general purpose use than just result caching.

1 comment:

Anonymous said...

That is sweet! =)