Memonymous: a Ruby memoization module that doesn't clutter up its includees.
Public domain (more or less). See the LICENSE file for more information.
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
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.
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
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.