event_jr
event_jr

Reputation: 17707

How to memoize a class method with META PROGRAMMING?

EDIT: To be clear. This is a question about how to do something with meta programming. It's not about memoizing. Clearly there are better ways to memoize. The relevant methods have "memoize" in them just to illustrate their purpose.


I'm just toying around with meta programming, so please don't answer use a @foo instance variable.

I have the following that tries to memoize both an instance and a class method by overwriting the method definition from the running method..

class Obj
  class << self

    def meta_me; self; end

    def class_memoize
      puts "hard core calculating ..."
      abc = "huge calculation result"

      raise "broken here with infinite loop"

      define_class_method "class_memoize" do
        puts abc
        abc
      end
      class_memoize
    end

    def define_class_method name, &blk
      meta_me.instance_eval do
        define_method name, &blk
      end
    end
  end

  def instance_memoize
    puts "hard core calculating ..."
    abc = "huge calculation result"

    self.class.meta_me.send :define_method, :instance_memoize do
      puts abc
      abc
    end

    instance_memoize
  end
end

o = Obj.new
o.instance_memoize
# hard core calculating ...
# huge calculation result

o.instance_memoize
# huge calculation result

The instance version works, but the class version does not.

I've left in an attempt at the class version for reference.

Upvotes: 1

Views: 809

Answers (1)

marcus erronius
marcus erronius

Reputation: 3693

Ethics aside, It's much easier than you think. Your main issue is you're using the wrong thing for your meta_me method. Try this:

class Object
  def metaclass
    class<<self;self;end
  end
end

This is a fairly common monkeypatch when doing metaprogramming in Ruby. Now it's easy to reimplement a method, dynamically:

class Obj
  def self.class_memoize
    puts "calculating..."
    abc = "result"
    metaclass.send(:define_method, :class_memoize) do
      puts abc
      abc
    end
    class_memoize
  end

  def instance_memoize
    puts "calculating..."
    abc = "result"
    metaclass.send(:define_method, :instance_memoize) do
      puts abc
      abc
    end
    instance_memoize
  end
end

As you can see, once you have the metaclass object, redefining the methods done the same way whether it's a class method or an instance method. Note that if you don't want to sully the whole namespace with a metaclass method, it's probably better just to use the class<<self;self;end idiom whenever you want to refer to it. You can call methods directly on it, like this:

(class<<self;self;end).send(:define_method, :foo){|bar| bar*23}

Note that the parens aren't truly needed, it just helps to contain the messiness that is the bare metaclass reference :) Hope this helps.

Upvotes: 1

Related Questions