Building Blocks in Ruby

Posted October 4, 2007

One of the things that most often seems to trip up new Ruby coders is the idea of blocks. Linguistically, they’re a pretty unique feature; I think Smalltalk is the only other language that has them.

They’re also similar to to functional constructs, but distinct enough to be a bit confusing on the surface. In addition, a lot of new folks coming to Ruby are probably used to languages even less functional.

So, in this post, I want to try to help clear this up. Blocks are actually really simple, but I think it helps to understand why blocks exist. To do this, I’m going to use a not-ruby language with not-blocks: Javascript.

To begin, let’s think about functions. Here’s a function:

function turnIntoStrings(array) {
  var newArray = [];
  for (var i = 0; i < array.length; i++) {
    newArray[i] = array[i].toString();
  }
  return newArray;
}

This function is pretty self-explanatory. It takes an array of anything and returns an array of the same stuff, changed into a string. turnIntoStrings([1, 2, 3]) returns ["1", "2", "3"].

Now, this function has a name. We named it turnIntoStrings. When we call it, we do so by using its name: turnIntoStrings([1, 2, 3]).

But in Javascript, functions don’t have to have names. Check this out:

function(array) {
  var newArray = [];
  for (var i = 0; i < array.length; i++) {
    newArray[i] = array[i].toString();
  }
  return newArray;
}

Same function as before, but without a name. This is called an anonymous function. This is just a value, like a string or an array. Except it can be called, the same way a normal named function can: by putting parentheses after it.

(function(array) {
  var newArray = [];
  for (var i = 0; i < array.length; i++) {
    newArray[i] = array[i].toString();
  }
  return newArray;
})([1, 2, 3]); // This returns ["1", "2", "3"], just like when it had a name.

You can also pass anonymous functions as arguments to other functions. If you’re not familiar with this sort of thing, it may sound sort of crazy. But just remember that they’re just like any other value, except that they can be called. Here’s an example:

function callOnArray(func) {
  return func([1, 2, 3]);
}

callOnArray(function(array) {
  var newArray = [];
  for (var i = 0; i < array.length; i++) {
    newArray[i] = array[i].toString();
  }
  return newArray;
}); // Once again, this returns ["1", "2", "3"], because it's just calling the anonymous function.

If you can make sense of those parentheses, you’ll see that our anonymous function is just being passed as an argument to callOnArray. callOnArray then takes its argument, our function, and calls it on [1, 2, 3] just like we did before.

Now, this might seem like a silly example. That’s ‘cause it is. But we can use it to do something more useful: make our original function much simpler.

Often when we’re dealing with arrays, we want to change each of the elements in a certain way. turnIntoStrings is an example of this; it changes each element into a string. Every time we want to do this, we write something out like so:

var newArray = [];
for (var i = 0; i < array.length; i++) {
  newArray[i] = /* Do something to array[i] */;
}
return newArray;

“Do something to array[i]?” What do we know that does something? Functions, of course! Using anonymous functions, we can move all this code into a single function:

function map(array, func) {
  var newArray = [];
  for (var i = 0; i < array.length; i++) {
    newArray[i] = func(array[i]);
  }
  return newArray;
}

Now all we have to do is call map and tell it, using an anonymous function, what we want to happen to each element. It makes turnIntoStrings five times smaller.

function makeIntoStrings(array) {
  map(array, function(element) { return element.toString(); });
}

Okay! Aside from the kinda confusing nested brackets and parentheses, that’s pretty cool! But what’s it got to do with Ruby?

Well, for starters, Ruby has anonymous functions, too. The syntax is a little funky. Here’s a translation of turnIntoStrings:

proc do |array|
  new_array = []
  i = 0
  while i < array.length
    new_array[i] = array[i].to_s
    i += 1
  end
  new_array
end.call([1, 2, 3]) # Returns ["1", "2", "3"], just like in Javascript.

There are a few confusing bits here. First, proc is how you make an anonymous function. It’s like function in Javascript, except you can’t use it to create named functions.

do and end contain the function; they could be replaced with { and } if we wanted. Usually one uses do and end for functions that are more than one line, while { and } are reserved for one-line functions.

You might notice that |array| bit. That’s the argument list. Unlike Javascript, in Ruby the argument list is inside the function, but before any actual code. Aside from that, and being bounded by | instead of ( and ), it’s just a normal parameter list.

Also, there’s no return. In Ruby, the last statement (in this case new_array) is always returned, so there’s no need to state it explicitly.

Finally, in Ruby you can’t just use () to call an anonymous function. For various reasons too complicated to get into how, you have to use the call method.

So now that we have a handle on the syntax, let’s try defining map in Ruby.

def map(array, func)
  new_array = []
  i = 0
  while i < array.length
    new_array[i] = func.call(array[i])
    i += 1
  end
  new_array
end

def turn_into_strings(array)
  map(array, proc { |element| element.to_s })
end

Now, that’s all well and good. But just like in Javascript, the nesting is kind of annoying. It would be great if we could just tack on that proc after the arguments, rather than shove it in alongside.

Well, it turns out, we can. All we have to do is add an argument at the end of map that begins with an ampersand (&). This says, “I’ll take a proc after the arguments.” That proc is than passed as the argument.

def map(array, &func)
  new_array = []
  i = 0
  while i < array.length
    new_array[i] = func.call(array[i])
    i += 1
  end
  new_array
end

def turn_into_strings(array)
  map(array) { |element| element.to_s }
end

Remember how we were going to talk about blocks? Well, that’s a block.

Yup. Really. Just nice syntax for anonymous functions. That’s all there is to it.

Okay, okay, that’s not quite all there is to it. You can do it all this way, but you don’t have to. If you’re doing simple stuff, Ruby makes it even easier.

Most of the time, all you’ll ever need to do is pass an argument to an anonymous function and get back the result. Ruby provides a keyword for this: yield. yield is a shortcut for func.call. You pass values to yield, yield passes them to the block, the block returns a value, yield returns the value.

Only, because you don’t need to know the name of the function to call yield, you also don’t have to add it as an argument. That’s pretty cool.

def map(array)
  new_array = []
  i = 0
  while i < array.length
    new_array[i] = yield(array[i])
    i += 1
  end
  new_array
end

def turn_into_strings(array)
  map(array) { |element| element.to_s }
end

And that’s all you really need to know about blocks. There’s certainly a lot more to learn, but this’ll give you a good grounding.

Before I finish up the entry, though, I have a few notes for you now that you (hopefully) more or less understand blocks.

First, my definition of map isn’t really Ruby style. I had to find some way of iterating through all of the array’s indices, but in Ruby the canonical way of doing that uses blocks. There’s no parallel to Javascript’s for loop, so I faked it. Here’s a better definition:

def map(array)
  new_array = []
  array.each do |element|
    new_array << yield(element)
  end
  new_array
end

The each method of an array calls the block on each element of the array.

But you’d never write that map in real Ruby code, either. map is actually a built-in method to Ruby’s Array class. So the real defintion of turn_into_strings would be

def turn_into_strings(array)
  array.map { |element| element.to_s }
end

Update: Also check out my follow-up post here.

Luke said October 05, 2007:

Thanks for the cool article!

Using javascript really helps to get the idea of what is happening.

Just out of curiosity wouldn’t this be more ruby’ish for your map function:

def map(array)
  new_array = []
  array.each do |item|
    new_array << yield(item)
  end
  new_array
end

Or if you needed the index then I would tend to use each_index instead.

Nathan said October 05, 2007:

Yeah, that’s true. I guess I wasn’t thinking; I’ll update the post.

Jon Leighton said October 06, 2007:

Heads up:

Yes (returns “hi”):

(function(foo) {
  return "hi";
})("bla");

No (returns “bla”):

function(foo) {
  return "hi";
}("bla");
Anonymous said October 06, 2007:
def turn_into_strings(array)
  array.map { |element| element.to_s }
end

You need to remove the “return” so that all elements are converted.

Nathan said October 06, 2007:

Jon: Huh. I could have sworn I tested those snippets. Fixed.

Anonymous: I also thought that Ruby blocks dealt with “return” the same way functions did, and kept it in to keep similarity with Javascript functions. I guess not.

Johnny P said October 09, 2007:

lambda and Proc overload [] and it works the same as call. So, you can lambda { puts “foo” }[], or func = Proc.new { |str| puts str }; func[“hello”]

Adriano said October 11, 2007:

Nathan, it turns out that Ruby actually has a parallel to Javascript for loop:

for i in 0...array.length
  new_array[i] = func.call(array[i])
end

The three dots in the range object excludes the last value, so that i goes from 0 to array.length – 1.

Nathan said October 11, 2007:

Those last two were actually conscious decisions. I didn’t want to muddle it up more by getting into operator overloading or ranges.

masone said October 13, 2007:

Wow, nice roundup. I think I never fully understood blocks and this helped me very much. Thanks!

Make your comments snazzy with Textile!