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.