People who come from languages like C, Java, Delphi, and C#, and use Ruby for the first time, tend to program in Ruby the same way they programmed in the language they already know well. As you read the post below, you'll see that languages with functional features, such as Ruby, offer their own ways of organizing code (the Ruby way). More specifically, in this article by Jon Dahl, you'll see more efficient ways to iterate (loop) in Ruby. You can find the original here. And now, the translation:
If you’re a programmer, you’ve probably worked through one or more books that taught you the syntax of a new language. I’ve had that experience with half a dozen languages, such as C, JavaScript, and Perl.
These books typically introduce loops in the middle of a syntax discussion, after data types and flow control, but before I/O and advanced features.
Loops are almost always introduced using this formula:
Introductory text: “What if you want to perform an operation more than once?”
Introduce the while loop, showing the difference between do while and while do.
Introduce the for loop, the crazy cousin of while.
(Bonus) Introduce the foreach loop if the language is high-level enough. And that’s it—you know how to loop through code, and it’s time to move on.
Not so fast. If you’re lucky enough to use a language with functional programming features, you shouldn’t use loops that way.
The point
From here on, I’ll use Ruby for the examples, but this article isn’t about Ruby. It’s about moving from primitive loops to iterating with collections, and about moving from general-purpose functions (like each) to more specific functions (like map).
From loops to methods that traverse the list themselves Over the last few months, I’ve been working on Tumblon, a medium-sized Rails application. Over the last three years, I’ve probably worked on 15–20 Ruby applications, totaling around 50,000 lines of Ruby code.
I’ve used a primitive loop only once.
That primitive loop was a loop {} loop, continually going through a task list looking for something to execute. In other words, a loop with no exit condition other than Ctrl+C or a server failure.
I’ve only used a simple loop once because each is generally a better option. This Ruby implementation will be familiar to anyone who has seen Ruby code before:
["horse", "pig", "cow"].each do |animal|
puts "Old MacDonald has a #{animal}"
end
(Yes, I have a young child.)
This is much more concise than the alternatives using for and while. It better represents what we’re doing: we’re not iterating with an exit condition; we’re interacting with the elements of an array. But what if you want to do something a fixed number of times? You can think of it as traversing this list: [1,2,3,4,5,6,7,8,9,10].each{}. Of course, Ruby has an even simpler version for that: 10.times{}.
So if your loop runs through some kind of list, each is the best abstraction for that problem. In my experience building Ruby applications, most loops traverse lists. Parsing XML? Iterating through a list. Adding numbers? Iterating through a list. Reading a text file? Reading STDIN? Working with rows in a database? Iterating through a list. That’s what the each loop does well.
Beyond arr.each
But each isn’t the final word. It’s a step up from the primitive for or while loops when working with a collection of values, but many each loops should be replaced with other array methods, such as map, inject, and select.
When is each useful? Simple: when you want to create effects outside the array, such as saving to a database, printing a result to the screen, or making a web service call. In these cases, you aren’t concerned with the return value; you want to change the state of the screen, disk, database, or something else. Take a look at this code:
User.find(:all).each do |user|
Notification.deliver_email_newsletter(user)
end
You don’t need the code above to return a value; you just need the email to be delivered.
But don’t use each if you want to extract a new value from an array. Instead, take a look at three other powerful methods: map, inject, and select.
To understand this, let’s look at the code below, which iterates through an array and creates a new array containing the elements that match a given condition, using each.
active_users = []
users.each do |user|
active_users << user if user.active?
end
active_users
The first and last lines are ugly. Why do you have to initialize and return active_users? The answer: because this is an abuse (misuse) of the each method. In this case, it’s much better to use select (or its equivalent, find_all):
users.select do |user|
user.active?
end
Using select makes the code shorter, easier to understand, and less error-prone. More importantly, it clearly encapsulates a common use of each (and looping in general).
Two other key functions—map and inject (or reduce)—complement select and follow a similar pattern. Unsurprisingly, they form the foundation of the MapReduce paradigm for distributed processing. I’ve written more about map and reduce in another article. Here’s a shortcut to knowing when to use these functions:
| Return value | Function |
| New array with the same number of values | map |
| New array made up of part of the old array | select |
| Single value (although this value may be an array) | inject |
| None | each |
Summary
Use each to change state. Otherwise, use functional array methods that return a value. That way, your code will be cleaner and less error-prone.
Learning to refactor
- Initialize an empty value, array, or whatever you need (
new_arr = []). - Use
arr.each, changing the initialized value. - Return the value (
return new_arr).
Whenever you spot this pattern, you know there’s an each loop that should be replaced.