Python Decorators in Ruby
This past week, I had a pretty long discussion on StackOverflow about Ruby and Python, that was touched off by question about whether there was anything special about Ruby that made it more amenable for Rails.
My initial response covered blocks (as a self-hosting way to improve the language) and the ability to define new "class keywords" because of the fact that class bodies are just normal executable code. The strongest argument against this was that Python decorators are as powerful as Ruby's executable class bodies, and therefore can be used to achieve anything that Ruby can achieve.
Python decorators do in fact provide a certain amount of additional power and elegance over the Python before decorators. However, this power is simply a subset of the available Ruby functionality. This is because Ruby has always had the ability to add features like decorators to the language without needing language changes.
In other words, while Python has added a new hardcoded feature in order to achieve some of the power of Ruby, adding such a feature to Ruby would be unnecessary because of the underlying structure of the Ruby language.
To show this clearly, I have implemented a port of Python function decorators in under 70 lines of Ruby. I wrote the code test-first, porting the necessary functionality from two Python Decorator tutorials written by Bruce Eckel in October 2008.
The list of features supported:
- Decorate any object that responds to call via:
decorate SomeKlass
ordecorate some_lambda
- A simpler syntax for class names that can be used as decorators:
SomeKlass(arg)
- The ability to give the decorator a name that could be used instead of the class name, if desired
Some examples:
class MyDecorator
def initialize(klass, method)
@method = method
end
def call(this, *args)
puts "Before MyDecorator with #{args.inspect}"
@method.bind(this).call(*args)
puts "After MyDecorator with #{args.inspect}"
end
end
class WithDecorator
# we could trivially add these to all classes, but we'll keep it isolated
extend MethodDecorators
MyDecorator() # equivalent to decorate MyDecorator
def my_function(*args)
puts "Inside my_function with #{args.inspect}"
end
end
WithDecorator.new.my_function(1)
When MyDecorator()
is called inside the WithDecorator
class, it registers a decorator for the next method that is defined. This is roughly equivalent to Python's function decoration. Some other examples:
class MyDecorator < Decorator
decorator_name :surround
def initialize(klass, method, *args)
@method, @args = method, args
end
def call(this, *args)
puts "Before MyDecorator(#{@args.inspect}) with #{args.inspect}"
@method.bind(this).call(*args)
puts "After MyDecorator(#{@args.inspect}) with #{args.inspect}"
end
end
class Class
include MethodDecorators
end
class WithDecorator
surround(1, 2)
def function(*args)
p args
end
end
WithDecorator.new.function(1)
In this case, I've inherited from the Decorator class, which allows me to add a decorator name, which can then be used in a class with MethodDecorators mixed in. In this example, I've mixed MethodDecorators into Class, which makes decorators available for all classes. Again, I could have made this the default, but I like to try to make new behaviors global if I can avoid it.
This is of course a first pass and I'm sure there are subtle inconsistencies between Python's decorator implementation and what I have here. My point is just that the feature that was added to Python to add flexibility is merely a subset of the functionality available to Ruby, because Rubyists can implement new declarative features without needing help from the language implementors, and have always been able to.