Genetically Modified Origin

George's Blog - Digital Adventures in The Big Apple

Summary - Extending Ruby With Ruby

| Comments

I find metaprogramming to be a fascinating subject. Code that writes itself! Keep DRY! Do crazy things in 10 lines that might otherwise take 100 or even be impossible!

Naturally, I was attracted to a presentation Michael Fairley gave entitled “Extending Ruby with Ruby”. Well, that and the fact that Michael’s name reminded me of Michelle Fairley, who plays Catelyn Stark on my favorite show, Game of Thrones.

His central thesis is that Rubyists usually think of metaprogramming as a tool used for two things – keeping DRY and saving some typing, and monkeypatching a library that doesn’t work as well as you would like. However, Ruby’s metaprogramming capabilities are much more powerful than this, and you can use Ruby to easily add new, awesome features to the language itself. He gives a few specific examples, drawing inspiration from other languages:

Function Decorators from Python

Python has a design pattern called “function decorators”, which allow us to inject or change code directly into a method, for additional clarity and to avoid writing a lot of boilerplate code. For example:

Function Decorator Example in Python
1
2
3
4
5
6
@transactional
def send_money(from, to, amount):
  from.balance -= amount
  to.balance += amount
  from.save()
  to.save()

This allows the send_money method to be wrapped in a “transactional” function, i.e. the send_money method won’t execute to the database unless every transaction in it is successful; otherwise all changes are rolled back. This is defined by the “transactional” function, defined elsewhere but used to “decorate” the top of this method. In Ruby we could use blocks or modify the method after the fact, but this is not clean as you can no longer trace execution of the method from top to bottom.

Luckily, even though Python has this cool feature that doesn’t exist in Ruby, we can add this functionality to Ruby itself given its extensible nature. Michael created a neat gem, method_decorators, which does exactly this (there is another gem which does something similar). The amazing thing is that the core added functionality is accomplished in a little more than a dozen lines of code! The result of that is we can now mimic function decorators in Ruby:

Function Decorator Added to Ruby
1
2
3
4
5
6
7
+Transactional
def send_money(from, to, amount)
  from.balance -= amount
  to.balance += amount
  from.save()
  to.save()
end

Partial Applications from Scala

In Scala, bulk operations on a collection of items (aka enumerables in Ruby), such as map, filter, and reduce, can be shorthanded using an underscore (“_”) for clarity. This is part of a concept known as partial applications:

Making code pretty in Scala
1
2
List(1,2,3).map(i => i.toString) // List("1", "2", "3")
List(1,2,3).map(_.toString)      // List("1", "2", "3")

Ruby has something similar:

Making code pretty in Ruby
1
2
[1,2,3].map{ |i| i.to_s } # ["1", "2", "3"]
[1,2,3].map(&:to_s)       # ["1", "2", "3"]

However, Scala has support for more complex versions of these anonymous functions which Ruby does not:

Making code pretty in Scala which doesn’t work in Ruby
1
2
3
4
5
List(1,2,3).reduce((i,j) => i + (j * 2)) // 11
List(1,2,3).reduce(_ + (_ * 2))          // 11

List(1,2,3).filter(i => 2 * i < 3)       // List(1)
List(1,2,3).filter(2 * _ < 3)            // List(1)

Once again, thanks to the power of Ruby metaprogramming, we can accomplish something similar in Ruby with just a few lines of code! The key is defining a “_” (underscore) method in the base Object class which can take a block (see slides 88-109 for details). With the added features, Ruby now has a similar functionality:

After monkeypatching Ruby
1
2
3
4
5
[1,2,3].reduce{ |i,j| i + (j * 2) } # 11
[1,2,3].reduce( &_{_ + (_ * 2)} )   # 11

[1,2,3].select{ |i| 2 * i < 3 }     # [1]
[1,2,3].select( &_{2 * _ < 3} )     # [1]

Now, monkeypatching the Object class with something as basic as an underscore is probably not the best idea, but it does show the power of Ruby in being able to extend itself and mimic other languages.

Lazy Evaluation from Haskell

The last example Michael gives is lazy evaluations in Haskell, e.g. not executing an http request until it is needed. We can easily define a Lazy class to be used as a wrapper (or even as a method decorator, ala the first example) to accomplish something similar:

Lazy Evaluation class
1
2
3
4
5
6
7
8
9
10
class Lazy < BasicObject
  def initialize(&block)
    @block = block
  end

  def method_missing(method, *args, &block)
    @result ||= @block.call
    @result.send(method, *args, &block)
  end
end

Now if compare these two blocks of code:

without Lazy Eval
1
2
3
4
5
6
7
8
def three
  puts "2"
  3
end

x = three
puts "1"
puts x
with Lazy Eval
1
2
3
4
5
6
7
8
9
10
def three
  Lazy.new do
    puts "2"
    3
  end
end

x = three
puts "1"
puts x

Now, for something absolutely bonkers, Michael changes the way Ruby as a language works so that EVERYTHING can use lazy evaluation, mimicking Haskell. He is able to do this via the ObjectSpace class and instance_methods method, which allows him to loop through every method in every module and class (with some exceptions):

Craziness in Ruby
1
2
3
4
5
6
7
8
9
10
11
modules = ObjectSpace.each_object(Module)

modules.each do |k|
  k.instance_methods.each do |m|
    next if ... # some exceptions
    im = k.instance_method(m)
    k.send(:define_method, m) do |*args|
      Lazy.new { im.bind(self).call(*args) }
    end
  end
end

This sounds a little dangerous to me, but it does have some practical applications. For example, in a simple Rails app which might fetch tweets from the Twitter API:

Lazy Evaluation applied to Rails
1
2
3
4
5
6
7
8
9
10
# Controller
def index
  @tweets = Lazy.new { get_from_twitter_api }
end

# View
<% @tweets.each do |tweet| %>
  <%= tweet.author_name %>:
  <%= tweet.text %>
<% end %>

Since we want the client browser to fetch the CSS and JS as quickly as possible, we can make the “get_from_twitter_api” request lazy.

The Power of Metaprogramming

Seeing these examples really made me appreciate the power of Ruby even more. Like many things in life, I think the best things should be easy to learn, but hard to master. It should cater to beginners (with convention over configuration) but still be powerful for the advanced (e.g. metaprogramming).

One of the reasons I am where I am now is that I believe in the power of software to change the world, the power to fix problems, and as programmers I think this mindset should apply everywhere – not just the big macro problems in the world, but the small, nitty-gritty micro problems as well. Software should facilitate answers to real-world huge problems such as “how do I get to the nearest train station?” or “what are my friends doing?”, but it should also facilitate answers to problems such as “can I get my programming language to behave the way I want it to?” Before you can change the world, you should be able to change yourself.

Comments