Generic-user-small Tony Eichelb... 2 posts

I am loving these screencasts and metaprogramming is starting to come to me now, which is really fun. So, Thanks Dave!

During epsiode 5 I tried to write the code before watching the implementation. Here is what I came up with for example 9. Instead of defining a method using the block and then using that to bind to the object later, I just called the block directly. Is there anything wrong with doing this?



module Memoize

  def remember(method, &block)
    memory = {}
    define_method(method) do |*args|
      return memory[args] if memory.has_key?(args)
      memory[args] = block.call(*args)
    end    
  end

end


 
Generic-user-small Bharat Ruparel 29 posts

Hello Tony,
I am not sure what the answer is to your question because I cannot run the program with your module definition above. May be you have made more changes in the program below where remember method is called?

Please see my program below with your changes:



module Memoize
  def remember(method, &block)
    memory = {}
    define_method(method) do |*args|
      return memory[args] if memory.has_key?(args)
      memory[args] = block.call(*args)
    end    
  end
end      

class Discounter
  extend Memoize

  remember :discount do |*skus|
    expensive_discount_calculation(*skus)
  end                                       

  private

  def expensive_discount_calculation(*skus)
    puts "Expensive calculation for #{skus.inspect}" 
    skus.inject {|m,n| m + n }
  end
end

d = Discounter.new
puts d.discount(1,2,3)

puts d.discount(1,2,3)
puts d.discount(2,3,4)
puts d.discount(2,3,4)      

d1 = Discounter.new
puts d.discount(1,2,3)


I get the following error message:

C:/NBProjects/MetaProgDaveThomas/lib/main.rb:15: undefined method `expensive_discount_calculation’ for Discounter:Class (NoMethodError) from C:/NBProjects/MetaProgDaveThomas/lib/main.rb:6:in `call’ from C:/NBProjects/MetaProgDaveThomas/lib/main.rb:6:in `discount’ from C:/NBProjects/MetaProgDaveThomas/lib/main.rb:28

I am not sure what the answer is but from the error message, but it seems to me that the way you have defined the module the dynamic method discount becomes a class method when the module is extended in the class, and a class method cannot call an instance method expensive_discount_calculation.

Just after I wrote this post, I decided to check if my theory is true and made the following change to the definition/declaration of expensive_discount_calculation method.

def self.expensive_discount_calculation(*skus)

so, you see, the only change that I made is I put a self in front of the method name making it a class method. It ran fine. So the question boils down to what is better for meta-programming defining an instance method dynamically or a class method? At least in Java world when you don’t need to define and isolate state, i.e., instance variables, we prefer to define class methods since they are shared by all instances of the class and means less overhead for the compiler/interpreter, so I tend to think that this is preferable in terms of efficiency if the same logic holds true in Ruby world. But I am not an experienced Ruby and/or Rails programmer so I can easily be wrong.

I hope you don’t mind me joining the thread since these are precisely the kind of questions that I am struggling with at this point. It would be great to hear what David has to say.

Regards,

Bharat

 
Generic-user-small James Whiteman 4 posts

“it seems to me that the way you have defined the module the dynamic method discount becomes a class method when the module is extended in the class”

:discount is still an instance method here. The problem is that the block is going to execute in the wrong context, in this case. You could move :expensive_discount_calculation into the metaclass to make it work (as in your example), but then you’d have to move any related methods & state there along with it.

 
Generic-user-small Tony Eichelb... 2 posts

Thanks for the input! Maybe I will go through the episode on blocks and Procs again to get a better understanding of the context it is executing in.

Bharat,

I also changed the Discounter class and did not post my change so that is why I didn’t get the same errors. Sorry about that.


module MemoizeModule

  def remember(method, &block)
    memory = {}
    define_method(method) do |*args|
      return memory[args] if memory.has_key?(args)
      memory[args] = block.call(*args)
    end    
  end

end

class Discounter
  extend MemoizeModule

  remember :discount do |*skus|
    puts "expensive call" 
    skus.inject { |m, n| m + n }
  end

end

d = Discounter.new
puts d.discount(1,2,3)
puts d.discount(1,2,3)
puts d.discount(12,4,3)


4 posts, 3 voices