Simplistic memoization for Ruby that doesn't clutter the method namespace with aliasing
Ruby
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
lib
test
.gitignore
.rvmrc
Gemfile
LICENSE
README.md
Rakefile
memonymous.gemspec
will_it_blend.rb

README.md

Memonymous: a Ruby memoization module that doesn't clutter up its includees.

License

Public domain (more or less). See the LICENSE file for more information.

Origin

Inspired by a recent Hangman puzzle at the Portland Ruby Brigade (link), I decided to try writing a memoization module that used super instead of mucking around with alias_method_chain.

Unfortunately, the only way I could think of to get this to work was to override the including class's .new method, so that all new instances were actually instances of a newly-created subclass...and that just seemed like a Really Bad Idea™.

Fortunately, I did find an interesting workaround, and in the process learned a few new tricks that have been sitting right there in the Kernel and Module classes just waiting for me to notice them.

Usage:

  class FrankSinatra
    attr_reader :call_count
    def dont_be_afraid_you_can_call_me
      @call_count ||= 0
      @call_count += 1
    end
  end

  class MemoizedFrankSinatra < FrankSinatra
    include Memonymous
    memoize :dont_be_afraid_you_can_call_me
  end

  naked_frank = FrankSinatra.new
  3.times do
    naked_frank.dont_be_afraid_you_can_call_me
  end
  p naked_frank.call_count # => 3

  memoized_frank = MemoizedFrankSinatra.new
  3.times do
    memoized_frank.dont_be_afraid_you_can_call_me
  end
  p memoized_frank.call_count # => 1

Technique

I was actually quite amused by this one. Because Ruby doesn't let you change the superclass of a Class object, I couldn't insert new methods above the includee in the hierarchy. Instead, when .memoize is called, it asks Ruby for an UnboundMethod object for each method it's supposed to memoize and hangs onto it in a class instance variable. Then it defines its own replacement for the same method.

Later, when you call the memoized method, we either look to see if we've already computed it (thus the 'memoization' part of the gem), or we grab the UnboundMethod, bind it to the caller, call it, and save the result. This process reminds me of the Wallace and Gromit film "The Wrong Trousers" -- see the comments in the Memonymous module for a link to a Google video search.

Thanks also to Jay Fields for the technique -- I had started down this path and was trying to figure it out, but came across a 2008 blog post with a conveniently-packaged code snippet.