Dynamic programming


#1

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


#2

Frank Tao wrote:

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 :slight_smile: 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.


#3

Brian C. wrote:

1) dynmically define class variable

I suggest: don’t use a class variable :slight_smile: 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 :wink:
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


#4

On Wed, Dec 3, 2008 at 5:04 PM, Ron F. removed_email_address@domain.invalid wrote:

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 :slight_smile: 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 :wink:
Correct, this should be thoroughly explained, well I will try :wink:
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.
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 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?
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 :wink:
class Supa
@@supa = :super
def self.supa; @@supa end
end

p Supa.supa

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

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

Cheers
Robert

Ne baisse jamais la tête, tu ne verrais plus les étoiles.

Robert D. :wink:


#5

Frank Tao wrote:

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”


#6

Brian C. wrote:

Frank Tao wrote:

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.

‘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.