An alternative way to Memoize in Ruby

May 8, 2017

I was screwing around with Ruby Coroutines, and the concept of coroutines in general, when I came across this page. It talks about how you can use coroutines to memoize functions. Ruby has native coroutines. You can guess what happened next.

Here’s what I came up with:

class Object
def self.fiber_memoize(method_name)
meth = self.instance_method(method_name)
self.send(:define_method, method_name) do
f = Fiber.new do |s|
result = meth.bind(self).call
loop do
Fiber.yield(result)
end
end
self.send(:define_singleton_method, method_name) do
f.resume
end
f.resume
end
end
end


This works on an instance-level, which a niave implementation wouldn’t. It also works by re-defining methods on a per-instance basis when you call them, which is just another example of how much Ruby rocks.

Here’s how to use it:

class Factorial
def initialize(num)
@num = num
end

def result
puts "This run is not memoized"
(1..@num).inject(:*) || 1
end

fiber_memoize :result
end

f = Factorial.new(100)
puts f.result #=> Prints the not memoized message, then the result
puts f.result #=> Prints the (now memoized) result


Interesting, this is also a way to do memoization in ruby without any conditionals, which is pretty neat.

Please never use this in any kind of production environment. I’m begging you.