Function Decorators in Ruby

Function decorators1 are cool. Put simply, a decorator “wraps” a function in order to extend its behavior without modifying the original function. Python has them built-in:

def memoize(fn):
  """ Decorator to memoize the result of a given function """
  cache = {}
  def memoizer(*args):
    if args not in cache:
      cache[args] = fn(*args)
    return cache[args]
  return memoizer

@memoize
def fib(n):
  """ Calculate the nth Fibonacci number """
  if n == 0 or n == 1: return n
  return fib(n-1) + fib(n-2)

With a bit of metaprogramming, we can achieve a similar result in Ruby:

# Decorator to memoize the result of a given function
def memoize(fn)
  cache = {}
  fxn = singleton_class.instance_method(fn)
  define_singleton_method fn do |*args|
    unless cache.include?(args)
      cache[args] = fxn.bind(self).call(*args)
    end
    cache[args]
  end
end

# Calculate the nth Fibonacci number
def fib(n)
  return n if n == 0 || n == 1
  return fib(n-1) + fib(n-2)
end
memoize :fib

In Ruby 2.1, def returns the name of the defined method2. We can make it even nicer:

memoize def fib(n)
  return n if n == 0 || n == 1
  return fib(n-1) + fib(n-2)
end

How cool is that?


1Intro to Python Decorators, by Bruce Eckel
2value of def-expr: Ruby feature #3753, implemented in Revision 42337