Becoming a Blockhead

Posted October 12, 2007

About a week ago, I tried my best to explain the ins and outs of blocks in Ruby. The basic gist was that blocks were snazzy syntax for anonymous functions.

So what makes blocks so cool? This time I want to take a look at a few of the uses blocks are put to in Ruby.

What’s more, we’re going to define all the functions we use that take blocks ourselves. Most of the functions we’ll define exist already in the standard Ruby implementation, but hopefully by seeing them defined first-hand, you’ll get a better understanding of why blocks are so useful.

Before we begin, here’s something snazzy about Ruby you might not have known. We’ll be making use of it in all of the examples. You can take a preexisting class, like Array, and add methods to it whenever you want. You can even override existing methods.

class Array
  def say_hello
    "Hello from #{self.inspect}!" 
  end
end

[1, 2, 3].say_hello #=> "Hello from [1, 2, 3]!"

Within a preexisting class, the self variable still refers to the current instance of that class, just like in objects you make from scratch.

Now, onto the blocks!

Counting

If you’ll recall, last time we tried to mimic Javascript’s for loop. It was pretty awkward. Wouldn’t it be nice if we had some method to count for us?

Well, we can make one, using the power of blocks. Let’s call it times; it sounds pleasantly English-like to say “5 times do whatever.”

So how do we want it to work? Probably a lot like our previous attempt at counting. Get an integer and keep increasing it until it hits the number we’re counting to. This’ll be self in the code. Let’s give it a go:

class Integer
  def times
    i = 0
    while i < self
      yield # Remember: this means "call the block" 
      i += 1
    end
  end
end

Sweet! Let’s give it a try:

# Print "Hello!" five times
5.times { puts "Hello!" }
# Hello!
# Hello!
# Hello!
# Hello!
# Hello!

There is one thing about this that’s a little sad. Sure, we’re calling the block five times, which is what we wanted to do. But we’re not being as useful as we could.

What if, for instance, we wanted to add up all the numbers less than 100? We’d have to keep a counter to keep track of which number we were on. But there’s already a counter in the method! Can’t we just use that?

We sure can; all we have to do is yield it:

class Integer
  def times
    i = 0
    while i < self
      yield i # This means "call the block and pass it i" 
      i += 1
    end
  end
end

Now it’s easy to add up the numbers:

sum = 0
100.times { |i| sum += i }
sum #=> 4950

But wait! Won’t that break our old use, where our block didn’t take an argument? Actually, it won’t: you’re allowed to yield more values than a block takes. Ruby won’t complain; everything will work.

This leads to a rule of thumb: when in doubt, pass more information to the block. It doesn’t have to use it.

Dealing With Arrays

The thing about arrays is that you never know how many elements one of them has. It could be one; it could be one million. And most of the time, we want to be able to do stuff to all of them.

Thus, programming languages provide a way to loop through the arrays. Ruby’s way is to use blocks. The typical format is a method called each that calls its block on each element in the array in turn. We can implement it using times:

class Array
  def each
    self.length.times { |i| yield self[i] }
  end
end

["Hello", "blocky", "world!"].each { |e| puts e }
# Hello
# blocky
# world!

All we’re doing here is yielding each successive element of the array.

each is actually such a useful method, there’s a special syntax that you can use for it. It’s Ruby’s own sort of for loop:

for e in [1, 2, 3]
  e += 5
  puts e
end
# 6
# 7
# 8

This is exactly the same as saying

do |e|
   e += 5
   puts e
 end

Now we can use each to implement map from last time:

class Array
  def map
    new_array = []
    self.each { |e| new_array << yield(e) }
    return new_array
  end
end

[1, 2, 3].map { |e| 4 - e } #=> [3, 2, 1]

There’s a lot more we could do with arrays, but for now let’s move on.

Resource Management

Due to some quirks of how file systems work, whenever you open a file in any langauge, you have to remember to close it when you’re done. Otherwise, bad things happen.

In most object-oriented languages, the way to do this is to call a close method on the file when we’re done with it. This works in Ruby, too:

file = File.new("/home/nex3/etc/blog")
lines = []
until file.eof?
  lines << file.gets
end
file.close

But that’s kind of annoying. You tend to sometimes forget to close the file, especially if there’s a lot of code in between opening and closing.

Blocks can come to the rescue here, as well. If we wrap the code using the file in a block, we can get it to automatically close when we’re done.

class File
  def self.open(filename)
    file = File.new(filename)
    yield file
    file.close
  end
end

Now we can write the previous bit of code like so:

File.open("/home/nex3/etc/blog") do |file|
  lines = []
  until file.eof?
    lines << file.gets
  end
end

Alright, this is all I have time for now. Look forward to yet another entry on blocks - this time on some crazy blocky magic - in the near future.

RainChen said October 15, 2007:

I think there is typo mistake:

File.new("/home/nex3/etc/blog") do |file|
  ...
end

should be

File.open("/home/nex3/etc/blog") do |file|
Nathan said October 15, 2007:

Oh, good point. Fixed.

RainChen said October 19, 2007:

another : def open(filename)

open should be a class method: def self.open(filename)

LeMarsu said October 30, 2007:

Even if it is a little out of the context of the post, I’d rather write the File.open like this:

class File
  def self.open(filename)
    file = File.new(filename)
    begin
      yield file
    ensure
      file.close
    end
  end
end

Like this, if an exception is raised in the block. the file will still be closed.

Make your comments snazzy with Textile!