Python Decorators in Ruby Continued

By Loren Segal on July 07, 2009 at 715:812:06 AM

I came across Yehuda Katz’s post about implementing Python-style decorators in Ruby and it certainly piqued my curiousity. I’m aware of the technical feasibility of implementing this in Ruby (and how completely trivial it is), but what interested me the most is making it look as much like Python as possible, syntax-wise. The fun part here is that Ruby actually has a @style syntax; they’re instance variables, and you’re all about to see the horrible abuse I’ve put them through to get as close to Python decorator syntax as I believe is possible.

Prelude: this is really just a for-fun exercise. Please don’t try this at home (or at work, to be specific; only try this at home). Using this code pretty much renders your Ruby interpreter unusable for any other computations of value. But hey, the fun is in getting it to work and never using it again, right?

The Examples

So without getting into implementation details, I’ll start by showing the final syntax, and then we can work back to how it was done (if you care). The following is the equivalent of a @classmethod decorator in Python, but it’s pure Ruby (1.9) code:

require 'class_method_decorator'

class Foo
  @classmethod.()
  def bar
    puts "class method #{self}.bar: #{method(:bar)}"
  end
end

Foo.bar
#=> "class method Foo.bar: #<Method: Foo.bar>"

Yes, I could have just defined the method as self.bar. If that's bothering you, you've completely missed the point.

So there it is, only 3 characters off of the Python equivalent, not so bad. Note also that it behaves correctly (which isn’t really the point, but it makes things more fun). If we call Foo.new.bar it will raise a NoMethodError. There’s a ton of interesting hackery in this one specific example, but I’ll get back to it later. Right now, let’s look at another example, this time it’s an HTTP Servlet:

require 'servlet_decorator'

class MyServlet
  @page.(method: :get, formats: [:html, :json])
  def index
  end

  @page.(method: :post)
  def create
  end
end

# Prints:
#   Page index has options {:method=>:get, :formats=>[:html, :json]}
#   Page create has options {:method=>:post}

This one is only 1 character off of the Python equivalent, that tiny little period. Again, not so bad— frankly, it’s kind of cool.

How It Works

Once again, note that this exercise is not about how it works, but simply that it does. Look, it works! Yay! You may now move on and skip to the end.

…What, you’re still here? Okay fine, I can only assume that if you’re still reading you’re at least mildly interested in what’s going on, and I’m not one to keep secrets, so lets go— but I’m warning you, it’s ugly.

First, the answer is yes. Those are, in fact, instance variables. They are class instance variables, to be specific. What am I doing? It’s simple, I’m creating a bunch of instance variables and assigning a Proc (Method to be specific) to them in every class inherited from Object (yes, every class, I told you it was ugly). The .() syntax is a new Ruby 1.9 syntax that until now I thought was hideous; now I happen to think it’s awesome. It’s basically the equivalent to .call(). The rest of this stuff is pretty much just modeled after Yehuda’s implementation, the Decorator base class responds to the #call and does its own magic decorator stuff.

If you want to see the actual implementation, the code is on Github. Run the examples.rb file, but make sure you have Ruby 1.9. For those too lazy to click links, here’s what the @page decorator looks like:

class Page < Decorator
  def decorate(klass, meth, opts = {})
    puts "Page #{meth} has options #{opts.inspect}"
  end
end

And here is the @classmethod decorator:

require 'force_bind'

class ClassMethod < Decorator
  def decorate(klass, meth)
    proc = klass.instance_method(meth).force_bind(klass)
    klass.define_singleton_method(meth, &proc)
    klass.send(:undef_method, meth)
  end
end

Awesome, but we’re missing one more secret.

The Secret of Binding Singleton Methods

Stop reading here if you're not interested in Ruby meta-programming. I told you to stop reading a while ago, though, so you probably won't listen this time either.

Those of you really keen on Ruby syntax and meta-programming might have noticed that in my @classmethod decorator example I took an instance method and redefined it as a singleton method (class method, colloquially). Those of you who are truly intimate with the innards of Ruby might have seen the unknown method force_bind used on an UnboundMethod object (yea, that’s what instance_method returns). There’s some trickery here that requires a C extension to get working, and yes, I wrote the extension specifically for the decorators example.

So what exactly am I doing, and what’s so special about force_bind?

Well UnboundMethod#bind is (oddly) strict about what it can bind an unbound method to. The rule is:

Unbound methods can only be called after they are bound to an object. That object must be be a kind_of? the method‘s original class.

The object you bind to must be of the same “type” as the original method’s owner, which means a method of class A can only be bound to class A (or subclasses). Remember that singleton classes are not the same type as their instances, meaning an instance of A is not the same type as class A. Long story short, Ruby won’t let you rebind an instance_method to its own class.

The weird part here is that there is no technical reason why this is not the case. It is simply an argument type check that occurs, but is not necessary. I created a C extension called force_bind which removes this type-checking and allows you to rebind an instance_method (or any UnboundMethod) to any other object. It works just fine (so far).

Why didn’t I just override kind_of? like the docs say to? Well, it doesn’t actually check kind_of?, it calls the Ruby internal method rb_obj_is_kind_of, which means you can’t override the behaviour in Ruby. That makes me sad. But then, so does the type-checking.

I can’t really explain why UnboundMethod#bind is implemented the way it is, I can only guess that allowing the rebinding of methods to other classes opens the door for some crazy edge-cases and potential memory-leaks; of course that’s never stopped Ruby before.

Conclusion

So there you go, the most Pythonic decorators Ruby will allow. And one last time, a reminder that this was really just a for-fun exercise. If I somehow hurt your feelings through my flagrant abuse of Ruby/Python syntax, it wasn’t intentional.

Questions? Comments? Follow me on Twitter (@lsegal) or email me.