Dynamic programming

# 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
--
Posted via http://www.ruby-forum.com/.

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.

···

--
Posted via http://www.ruby-forum.com/\.

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"
--
Posted via http://www.ruby-forum.com/\.

Brian Candler 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.

# 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:
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

···

--
Ron Fox
NSCL
Michigan State University
East Lansing, MI 48824-1321

Brian Candler 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.

···

--
Posted via http://www.ruby-forum.com/\.

Brian Candler 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.

# 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

···

On Wed, Dec 3, 2008 at 5:04 PM, Ron Fox <fox@nscl.msu.edu> wrote:
--
Ne baisse jamais la tête, tu ne verrais plus les étoiles.

Robert Dober :wink: