Transparent Caching Idiom


#1

I’ve made good use of this idiom recently, and wanted to share it.

Once this code is active, any method named with a “_c” suffix is
automatically cached. The method is assumed to follow a simple
contract: It performs no side-effects / mutations, and it’s arguments
properly implement “Object.hash”.

This is specifically useful for expensive but functional calculations.
Enjoy!

–Ben (removed_email_address@domain.invalid)

class Class

alias old_new new
def new(*args, &block)
    Class.defc_wrap(old_new(*args, &block))
end

def Class.defc_wrap(obj)
    klass = obj.class
    defc_methods = obj.methods.select { |n| n =~ /_c$/ }
    defc_methods.each do |name|
        meth = obj.method(name)
        wrapped_name = Class.defc_next_name(klass)
        cache = klass.class_eval("@#{wrapped_name} = {}")
        wrapper = Proc.new do |*args_a|
            arg_hash = args_a.hash
            if cache.has_key?(arg_hash)
                cache[arg_hash]
            else
                cache[arg_hash] = meth.call(*args_a)
            end
        end
        klass.send(:alias_method, wrapped_name, name)
        klass.send(:define_method, name, wrapper)
    end
    obj
end

def Class.defc_next_name(obj)
    klass = obj.class
    i = 1
    i += 1 while obj.respond_to?("_defc_#{i}") ||

obj.instance_variables.member?("@defc#{i}")
defc#{i}”
end

end


#2

Daniel S. wrote:

def Class.defc_wrap(obj)
            else
    klass = obj.class

Neat. Though it would be cool if the methods that were to be cached were
Daniel
I got this bookmarked, When i get time, i’ll have to compare to prior
thread on memozing
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155159
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155170


#3

beng wrote:

–Ben (removed_email_address@domain.invalid)
klass = obj.class
cache[arg_hash] = meth.call(*args_a)
i = 1
i += 1 while obj.respond_to?(“defc#{i}”) ||
obj.instance_variables.member?("@defc#{i}")
defc#{i}”
end

end

Neat. Though it would be cool if the methods that were to be cached were
chosen through a class method rather than a method name suffix.

class Klass
def foo; end
def bar; end
cached_method :foo, :bar
end

Cheers,
Daniel


#4

Gene T. wrote:

calculations. Enjoy!

                cache[arg_hash]
def Class.defc_next_name(obj)

Cheers,
Daniel
I got this bookmarked, When i get time, i’ll have to compare to prior
thread on memozing
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155159
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155170

See also:
http://raa.ruby-lang.org/project/memoize/

Kind regards

robert

#5

beng wrote:

–Ben (removed_email_address@domain.invalid)
klass = obj.class
cache[arg_hash] = meth.call(*args_a)
i = 1
i += 1 while obj.respond_to?(“defc#{i}”) ||
obj.instance_variables.member?("@defc#{i}")
defc#{i}”
end

end

This is a touched-up version of your code.

class Class
alias_method :new, :new

def cached_method(*methods)
@cached_methods ||= []
@cached_methods += methods
@cached_methods.uniq!
end

def new(*args, &block)
obj = new(*args, &block)
klass = obj.class
@cached_methods ||= []
@cached_methods.each do |name|
meth = obj.method(name)
i = 1
while klass.instance_variables.member?("@defc#{i}")
i += 1
end
wrapped_name = “defc#{i}”
cache = klass.class_eval("@#{wrapped_name} = {}")
wrapper = Proc.new do |*args_a|
arg_hash = args_a.hash
if cache.has_key?(arg_hash)
cache[arg_hash]
else
cache[arg_hash] = meth.call(*args_a)
end
end
klass.send(:alias_method, wrapped_name, name)
klass.send(:define_method, name, wrapper)
end

 return obj

end
end

class Klass
def foo; end
def bar; end
cached_method :foo, :bar
end