Friday, January 14, 2005

Why Ruby Is Neat

by Asim Jalis

The neat thing about ruby is its blocks and yields. So for example a function f, which takes three arguments and a block can be called as: f(a, b, c) { |x,y| more stuff }. This is what f looks like: def f(a,b,c) # do stuff with a,b,c z = yield x, y # do more stuff end Now yield can be inside a loop. Each time yield is called, it calls the block, and sets its two parameters to x and y. The final value of the block is assigned to z. This seems like a really neat idea and it makes many algorithms much cleaner. For example there is a regex function which calls the block with each one of its matches. The neat thing about the yield is that it allows you to embed your code in the middle of a function. Normally, you either control what happens before the function, or what happens after it, but you cannot modify something that happens in the middle of the function. The block/yield idea has some interesting applications. For example, this can be used to write powerful unit tests. Consider the Unix select, which takes a list of arguments, prints them on the screen and then asks the user for an input. If the user enters a number it returns that argument, otherwise it returns the empty string. Normally, this is not unit testable because of the user interaction. However, using Ruby's blocks this becomes quite easy to unit test. Simply write it as: option = select(options){ read input } The block reads a line of user input. The select function now prints the options, yields to the block, and then if the response was in the acceptable range returns the response, otherwise it can present the options to the user again, and ask him to try again. Unit testing this is easy. assert_equals("a", select(["a","b","c"]}{ 1 } ) assert_equals("b", select(["a","b","c"]}{ 2 } ) assert_equals("c", select(["a","b","c"]}{ 3 } ) assert_equals("" , select(["a","b","c"]}{ "q" } ) The last test is to allow a user to quit by typing "q". A more complicated test could have a block that maintained some state and returned different numbers on different yields. The other things that blocks make really easy is processing collections. For example, playing with the registry, which is normally a painful operation, becomes nearly trivial in Ruby. Similarly, traversing a mailbox filled with messages is really natural and easy. Today I am planning to play with MAPI, which is Exchange's mail API, and what I can do with Ruby there. I might be able to list all my messages, perhaps compute some interesting statistics on them. I am planning to just play and see what happens. The other natural applications for this kind of collections processing are directory structures. Also processing HTML becomes really easy. For example, I can read the HTML and then say: html.scan(/]*>/){ |i| }, and this will call the block on each image in the HTML. For some reason this seems really powerful. I have to figure out a good application for this.