dynamic programming

F

Frank Tao

# I have a class Adam
# I want to modify the method("m_a") so that it will return the cached
result
# If no cached result is available, then return the original result
# I want to create a class method(AKA: macro) to make it DRY
#
# So far, I have issues like
# 1) dynmically define class variable
# 2) failed to include a module inside a method of a class
#
# Any suggestion or comments will be appreciated
#
class Adam
def self.m_a
["Adam#m_a"]
end
end

class CachedAdam
caching_method :adam, :m_a

def self.caching_method
# this method should do something to make the following codes
end
end


## CachedAdam#caching_method should make and load the following codes
#
# module AdamWithCache
# def m_a_with_cache
# CachedAdam.get_cached_m_a || m_a_without_cache
# end
# def self.include(base)
# base.alias_method_chain :m_a, :cache
# end
# end
# Adam.class_eval{ include AdamWithCache }
#
# class CachedAdam
# def self.get_cached_m_a( adam_id )
# @@cache && @@cache[adam_id]
# end
#
# def self.set_cached_m_a(hash_list)
# hash_list.each do |k,v|
# @@cache ||={} # failed, any suggestion?
# @@cache[k] = v
# end
# end
# end
 
B

Brian Candler

Frank said:
# I have a class Adam
# I want to modify the method("m_a") so that it will return the cached
result
# If no cached result is available, then return the original result
# I want to create a class method(AKA: macro) to make it DRY

You might want to look at the 'memoize' method in ActiveRecord trunk.
# So far, I have issues like
# 1) dynmically define class variable

I suggest: don't use a class variable :) An instance variable of the
class would be fine. But personally I wouldn't keep the memoized values
in the class; I'd keep them in the instances themselves.
# 2) failed to include a module inside a method of a class

Look at Module.included for this, as shown below.

There are probably cleaner and/or more efficient ways than the
following, but it demonstrates the principle.

module Cache
def self.included(base)
base.extend ClassMethods
end

module ClassMethods
def caching_method(m)
name = "orig_#{m}"
var = "@__#{m}"
alias_method name, m
define_method(m) { |*args|
return instance_variable_get(var) if
instance_variable_defined?(var)
instance_variable_set(var, send(name,*args))
}
end
end
end

class Adam
include Cache
def foo
rand(100)
end
caching_method :foo
end

a = Adam.new
p a.foo
p a.foo
p a.foo

Beware of what you really want here though. If foo takes arguments, do
you want foo(1) and foo(2) to be able to return different values? Do you
want them both to be cached? If so, I leave that as an exercise for you.
 
R

Ron Fox

Brian said:
You might want to look at the 'memoize' method in ActiveRecord trunk.


I suggest: don't use a class variable :) An instance variable of the
class would be fine. But personally I wouldn't keep the memoized values
in the class; I'd keep them in the instances themselves.
I suggest: don't make blanket statements like that ;-)
That depends on what he wants the cache to accomplish. If the cache
should cache across all instances of Adam, a class variable is
_exactly_ what he wants. If it should only cache for the specific
instance of Adam an instance variable is what he wants.
If there will be many instances of Adam, and if Adam can peform
operations that invalidate cache then a global cache is so much easier
to handle than one distributed over all instances of Adam...since two
instances of adam may have the same item in their caches, and if
instance one invalidates.. how do you find/invalidate the item in
instance 2?

RF
 
F

Frank Tao

Frank said:
# I have a class Adam
# I want to modify the method("m_a") so that it will return the cached
result
# If no cached result is available, then return the original result
# I want to create a class method(AKA: macro) to make it DRY
#
# So far, I have issues like
# 1) dynmically define class variable
# 2) failed to include a module inside a method of a class
#
# Any suggestion or comments will be appreciated
#
class Adam
def self.m_a
["Adam#m_a"]
end
end

class CachedAdam
caching_method :adam, :m_a

def self.caching_method
# this method should do something to make the following codes
end
end


## CachedAdam#caching_method should make and load the following codes
#
# module AdamWithCache
# def m_a_with_cache
# CachedAdam.get_cached_m_a || m_a_without_cache
# end
# def self.include(base)
# base.alias_method_chain :m_a, :cache
# end
# end
# Adam.class_eval{ include AdamWithCache }
#
# class CachedAdam
# def self.get_cached_m_a( adam_id )
# @@cache && @@cache[adam_id]
# end
#
# def self.set_cached_m_a(hash_list)
# hash_list.each do |k,v|
# @@cache ||={} # failed, any suggestion?
# @@cache[k] = v
# end
# end
# end

THANKS FOR YOUR HELPFUL RESPONSE. I FOUND THAT MY ORIGINAL HAD SOME
MISTAKES AND THEREFORE DID NOT REFLECT EXACTLY WHAT I WANT.
SO I UPDATE IT AS FOLLOWS:

class Adam
def m_a
"a"
end
end

class CachedMethod
@@cached_variables = {}

def self.caching_method(model, method)
model, method = model.to_s, method.to_s
@@cached_variables["#{model}_#{method}_cache"] = {}

(class << self; self; end).instance_eval do
define_method "set_cached_#{model}_#{method}" do |hash_list|
hash_list.each do |k,v|
@@cached_variables["#{model}_#{method}_cache"][k] = v
end
end
define_method "get_cached_#{model}_#{method}" do |model_id|
@@cached_variables["#{model}_#{method}_cache"] &&
@@cached_variables["#{model}_#{method}_cache"][model_id]
end
end


#
# Purpose: create a module and let it be included by Adam class
# NOT WORKING, any suggestion?
self.class_eval <<-EOD
module AdamWithCache
def m_a_with_cache
CachedMethod.get_cached_adam_m_a(self.id) || m_a_without_cache
end
def self.include(base)
base.alias_method_chain :m_a, :cache
end
end
Adam.class_eval{ include AdamWithCache }
EOD
end

def self.reset
@@cached_variables = {}
end

self.caching_method :adam, :m_a

end

#module AdamWithCache
# def m_a_with_cache
# CachedMethod.get_cached_adam_m_a(self.id) || m_a_without_cache
# end
# def self.include(base)
# base.alias_method_chain :m_a, :cache
# end
#end
#Adam.class_eval{ include AdamWithCache }


#
#Example usage:
#=============================================

puts @adam_1 = Adam.new #==> assume: @adam.id == 1
puts @adam_2 = Adam.new #==> assume: @adam.id == 2
puts @adam_3 = Adam.new #==> assume: @adam.id == 3
puts @adam_1.m_a #==> "a"
puts @adam_2.m_a #==> "a"
puts @adam_3.m_a #==> "a"
puts @adam_1.id #==> 1
puts @adam_2.id #==> 2
puts @adam_3.id #==> 3

hash_list = {@adam_1.id => "b", @adam_2.id => "c"}
CachedMethod.set_cached_adam_m_a(hash_list)
puts CachedMethod.get_cached_adam_m_a(@adam_1.id) #==> "b"
puts @adam_1.m_a #==> "b" !!! DID NOT OUTPUT AS I EXPECTED
puts @adam_2.m_a #==> "c" !!! DIDO
puts @adam_3.m_a #==> "a" !!! DIDO

CachedMethod.reset
puts @adam_1.m_a #==> "a"
puts @adam_2.m_a #==> "a"
puts @adam_3.m_a #==> "a"
 
R

Robert Dober

I suggest: don't make blanket statements like that ;-)
Correct, this should be thoroughly explained, well I will try ;)
That depends on what he wants the cache to accomplish. If the cache shou= ld
cache across all instances of Adam, a class variable is _exactly_ what he
wants. If it should only cache for the specific instance of Adam an
instance variable is what he wants.
This might indeed be what he wants, but if it is he should be aware of
the implications that it has. It is against one of the most valued
principles of OO design, a class shall nothing know about its
subclasses (1). Now I agree that occasionally this principle shall be
violated for good reason. But I have not yet seen a use case for class
variables and IIRC not many have on this list. That is why by
instinct, and instinct can be wrong of course, many of us warn against
class variables, and sometimes without thorough explications.
If there will be many instances of Adam, and if Adam can peform operatio= ns
that invalidate cache then a global cache is so much easier to handle tha= n
one distributed over all instances of Adam...since two instances of adam = may
have the same item in their caches, and if instance one invalidates.. how= do
you find/invalidate the item in instance 2?
And how would that be solved by class variables if I may ask?

(1) this is not necessarily easy to see, but assume this code and
please forgive me for shouting ;)
class Supa
@@supa =3D :super
def self.supa; @@supa end
end

p Supa.supa

class Dupa < Supa
@@supa =3D :duper
def self.supa; @@supa end
end

p Dupa.supa
p Supa.supa ### AND THAT REALLY HURTS

Cheers
Robert
--=20
Ne baisse jamais la t=EAte, tu ne verrais plus les =E9toiles.

Robert Dober ;)
 
F

Frank Tao

Brian said:
You might want to look at the 'memoize' method in ActiveRecord trunk.

'memoize'? Do you mean by the one in Rails 2.2 under
ActiveSupport::Memoizable ?

I also found Rails.cache or RAILS_CACHE in Rails 2.1 very helpful.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top