Saturday, January 12, 2008

Ruby Meta-Programming: a bread and butter guide (Part 1)

I was on the Rails forum the other day and someone was looking for Ruby meta-programming articles. I suggested "The Ruby Way," a fantastic book with a good section on meta-programming, but I'm surprised I had nothing but a book to suggest. So I asked myself, "Self, why hasn't page rank stumbled across a grand and all encompassing meta-programming guide for ruby?" I have a guess: There aren't any. Ruby is, for the most part, SmallTalk reborn. But its hype has only come through the growing Rails community. Rails programmers, at least the new ones (which I think are the vast majority and will be for another 2 years or so) learn Rails and forget to learn Ruby. Ruby is not the cool thing to them. Plus, many come from a Java background so they don't even know what higher order procedures are. To them Lambda is the Ultimate unknown. It's not their fault though... I blame their parents. So I'm going to take some time to give some introductory examples and explanations on Ruby Meta Programming.

Higher Order Procedures: For those still writing java/jsp/php/asp code in Ruby a higher order procedure is a procedure that takes a procedure as an argument or returns a procedure. Read that carefully. I did not say it takes the evaluation of a procedure. This is not a higher order procedure:

def not_higher_order(arg1)
return performed_function(arg1)


The above passes the returned value of different_performed_function("blah") into not_higher_order which returns the return value of performed_function(arg1). Higher order procedures use procedures themselves (in Ruby the Proc object) as the argument and or return value, not its substitution after execution. So here is an example of a higher order procedure you must have used in Ruby. It's already built in to Enumerable Objects:

[1,2,3,4].collect {|i| i * 2 } # Returns [2,4,6,8]

Blocks are Ruby syntactic sugar for using higher order procedures. You use them all the time in Ruby, which is why you can do so much with Ruby so quickly without extensive knowledge and experience with a massive standard library. Every time you use a block you create an anonymous (unnamed) procedure and pass it into the called procedure (collect in this case) as an argument. As you will mostly use blocks instead of the object or Lambda keyword we will glaze over what those are at this point. However, you should look them up to get a better understanding of how procedures are first order objects in Ruby. Lets build our first higher order procedure after one that exists that you might not know about: Inject:

#Monkey patching Enumerable (will explain shortly) module Enumerable
#the ampersand declares the argument being passed is a proc object
module Enumerable
def inject(memo, &block)
#run enumerables each method and reset memo
#with the evaluation of the passed block
self.each {|enumerated_item| memo = yield(memo, enumerated_item) }
return memo

puts [1,2,3,4].inject(0) {|memo,item| memo + item } #returns 10 (the result of 0+1+2+3+4)
puts ["sam","likes","to","eat","bunnies"].inject("The person ") {|memo, item| memo + item} #returns "The person samlikestoeatbunnies"
puts [1,2,3,4].inject(8) {|memo,item| item % 2 == 0 ? memo + item : memo} #returns 14 (8+2+4)

What we just did is rebuild the inject method. We reopened the Enumerable module which is "mixed in" to Array, String, etc and overwrote the already existing inject method. The original and what we wrote do exactly the same thing. Then we used the & in front of the argument named block to let the ruby interpreter know that this is a procedure being passed as an argument. Because we use this special notation we can then call our new inject function with a block. (side note: You could also use keywords such as lambda to pass any procedure to any named argument. But often you'll only need/want one and so it's a nice clean way to do it.) Then we called the each method and used within it the yield keyword. This takes as its arguments the parameters you wish to pass to the block. You then catch those parameters in the block within the pipes: |memo, item|. The variable names in the pipes don't matter, just like in a collect or each statement. They are how you reference those values within the block itself.

If you've never done anything like this before take some time to experiment. See if you can recreate the Fibonacci numerical pattern and pass each iteration to an anonymous procedure (block). If you want to cheat there is an example of how to do this within the Ruby Pickaxe (Programming Ruby).

Higher order procedures are hugely important. If you are a rails programmer there is no better way to DRY up your views than by building block helpers and using concat and capture. If you plan to build a domain specific language (DSL) for some strange task in Ruby or Rails blocks are a must have so that you don't introduce 5000 extra keywords to Ruby and your documentation needs a fork lift to be moved. For a more in depth look at why and where you might want to use higher order procedures in any language check out the first chapter of "Structure and Interpretation of Computer Programming," also available in a video lecture.

1 comment:

  1. minor correction:
    [1,2,3,4].collect {|i| i * 2 } # Returns [2 (and not 1),4,6,8]