Aug 24 2008
Blocks: Not Just Twisted Loops/Iterators, part 1
As my interest in Ruby was growing, a friend, who’d read the pickaxe book, told me that not only did he not find blocks all that compelling, he felt they inverted control in an odd, inelegant way. I think he was saying that the code that should go on the “inside” was instead on the “outside”, and that somehow it was a bad programming practice.
But this view fails to appreciate one of the great contributions of functional programming, that functions (code) can be data. But you don’t have to wrap your mind around functional programming and ideas such as first class functions to appreciate blocks. And I hope to make the point by calling upon practical examples rather than theory.
In this sequence of posts I will compare and contrast how you’d do things with Ruby’s blocks and how you’d do the same thing in some other, more popular programming languages.
I’ve will look at four ways collections are processed in Ruby with blocks:
- listing
- filtering
- mapping
- sorting
And for this post I will be comparing Ruby with Java. Now of course there are generally multiple ways to do things in most programming languages. With respect to the Java examples, I’ll use the more standard or obvious Java solutions. In subsequent posts in this sereis, though, I’ll look at some Java alternatives and examine at least one additional language.
Ruby Supporting Code
We’ll need some data to manipulate, so let’s assume we have an Account class that models an account one might have at a bank.
class Account
attr_reader :number, :balance
attr_accessor :holder
def initialize number, balance, holder
@number, @balance, @holder = number, balance, holder
end
def to_s
@number.to_s + " : $" + format("%0.2f", @balance) + " (" + @holder + ")"
end
end
And let’s provide a method that returns some test data:
def get_test_accounts
[
Account.new(112111, 55000.00, "Moe Howard"),
Account.new(161112, 75000.00, "Larry Fine"),
Account.new(117113, -100.00, "Shemp Howard"),
Account.new(111814, 175.50, "Curly Howard"),
Account.new(111195, 2500.00, "Joe Besser"),
Account.new(111116, 56000.00, "Curly-Joe DeRita"),
]
end
Java Supporting Code
Now let’s provide the same supporting code in Java. Here’s the Account class:
public class Account
{
int number;
double balance;
String holder;
static final DecimalFormat balanceFormatter = new DecimalFormat("0.00");
public Account(int number, double balance, String holder)
{
this.number = number;
this.balance = balance;
this.holder = holder;
}
public String toString()
{
return number + " : $" + balanceFormatter.format(balance) + " (" + holder + ")";
}
public int getNumber() { return number; }
public double getBalance() { return balance; }
public String getHolder() { return holder; }
public void setHolder(String holder) { this.holder = holder; }
}
And here’s the code that supplies some test data (note: this code can return the data in the form of an Array, ArrayList, or HashSet):
public class TestAccounts
{
private static Account[] accountArray = {
new Account(112111, 55000.00, "Moe Howard"),
new Account(161112, 75000.00, "Larry Fine"),
new Account(117113, -100.00, "Shemp Howard"),
new Account(111814, 175.50, "Curly Howard"),
new Account(111195, 2500.00, "Joe Besser"),
new Account(111116, 56000.00, "Curly-Joe DeRita") };
private static List<Account> accountList = Arrays.asList(accountArray);
public static Account[] getArray()
{
return accountArray;
}
public static ArrayList<Account> getArrayList()
{
return new ArrayList<Account>(accountList);
}
public static HashSet<Account> getHashSet()
{
return new HashSet<Account>(accountList);
}
}
Listing
With the supporting code in hand, our first task will simply be to display all the accounts in the array. And with blocks we end up with code like this:
accounts = get_test_accounts
puts "List of all accounts:"
accounts.each do |account|
puts account
end
And of course we could have done the same thing with one of Ruby’s loop constructs as well.
How could we code this in Java? Well, there are a couple of ways. In this post I will focus on using Java’s loop constructs, but in my next post on this topic I’ll demonstrate another way of approaching this in Java. Here, though, is the loop example:
ArrayList<Account> accounts = TestAccounts.getArrayList();
System.out.println("List of all accounts:");
for (Account account : accounts) {
System.out.println(account);
}
There really isn’t much of a difference. Pretty much six of one and half-a-dozen of the other.
But blocks aren’t only used as simple iterators, and by looking at these other uses we’ll get the opportunity to highlight the power of blocks.
Filtering
What if we wanted to generate a list of those accounts with balances that are at least $10,000? Well here’s the Ruby code to do this, which uses Enumerable’s select method:
big_accounts = accounts.select { |account| account.balance >= 10000 }
In Java, using loops, we have a little more work to do, work that Enumerable#select has built into it. We need to create a collection to hold the result and manually insert the accounts that meet the criteria into this new collection:
ArrayList<Account> bigAccounts = new ArrayList<Account>();
for (Account account : accounts) {
if (account.balance >= 10000) {
bigAccounts.add(account);
}
}
Here the difference between the two languages is more significant. Filtering in Ruby can be a one-liner, where in Java we have to do quite a bit:
- create the filtered collection
- loop through the elements in the original collection
- selectively add items to the filtered collection
Mapping
Our next task will be mapping, sometimes called collecting, in which we take one collection of data and make a new collection of data through a mapping process.
For this task we will convert our array of Accounts to an array of Strings containing the holders first names, which we will determine by extracting all characters from the beginning of the string up to, but not including, the first space character.
So here is the Ruby code:
first_names = accounts.map { |account|
first_space = account.holder.index(' ')
account.holder.slice(0...first_space)
}
And here’s the equivalent Java code:
ArrayList<String> firstNames = new ArrayList<String>();
for (Account account : accounts) {
int firstSpace = account.holder.indexOf(' ');
String firstName = account.holder.substring(0, firstSpace);
firstNames.add(firstName);
}
The difference here is a little less obvious given that the internal logic is a little more complicated than in the filtering example above. But those same three additional steps are required — creating the destination collection, looping through the elements in the original collection, and manually adding items to the destination collection.
Sorting
Finally we will look at sorting our data by various criteria — first by account balance and then by the holder’s name.
Ruby:
accounts_by_balance = accounts.sort_by { |account| account.balance }
and:
accounts_by_holder = accounts.sort_by { |account| account.holder }
With Java we have a little more work to do. The Collections class has a sort method. And that method can either sort the data in one of two ways. If the data in the collection is inherently comparable (i.e., implements the Comparable interface) and that is the order that we want, then we just call sort. But if we want to sort the data in different ways, then we must supply a Comparator that tells the sort method the ordering of two specific items. We can either create a Comparator class and pass an instance of it in, or we can supply an anonymous inner class. In order to better approximate Ruby’s way of doing this, we’ll use the anonymous inner class.
Java:
// copy accounts array and sort the copy
List<Account> accountsByBalance = new ArrayList<Account>(accounts);
Collections.sort(accountsByBalance,
new Comparator<Account>() {
public int compare(Account a1, Account a2) {
return Double.compare(a1.balance, a2.balance);
}
});
and:
// copy accounts array and sort the copy
List<Account> accountsByHolder = new ArrayList<Account>(accounts);
Collections.sort(accountsByHolder,
new Comparator<Account>() {
public int compare(Account a1, Account a2) {
return a1.holder.compareTo(a2.holder);
}
});
Here the difference is pretty significant. With Java there is a lot of syntactic overhead compared to the Ruby one-liners. Now if we needed to do these sorts frequently, we would not use the anonymous inner class. We would instead create a regular Comparator class for each type of sort, and then pass instances into Collections.sort. So the code overhead is there for the first time the sort is needed, but that cost is amortized with each additional time that sort is needed.
Conclusions
I think a few points become apparent when comparing these examples. First, and perhaps most obviously, the Ruby code ends up being shorter. The second point addresses why. The Ruby methods that take blocks have all the common code in them — the code to iterate through the elements of the array along with the code to create the new collection in which the data will be placed. In the Java examples, it’s typically the case that the the programmer is responsible for those tasks, so the programmer ends up repetitively writing the code for them.
So yes, my friend is correct in that there is a bit of inverting the inside/outside in Ruby. But it’s there for a purpose. When the outside remains fixed and the inside changes, we want a way to supply the inside from the “outside”. And blocks provide a nice way to do this.
Update
Part 2 of this sequence is available.
If you would like to see the full source code for these examples, the Ruby source and the Java source code can be downloaded as zip files.

August 25th, 2008 at 6:10 am
There’s no reason why you can’t perform the ruby mapping example in one line also:
first_names = accounts.map {|account| account.holder.split(” “).first}
I’ve not used Java for a while but I would assume you can also drop a line at least from its example using split.
August 25th, 2008 at 10:28 am
Thanks; that’s a good point, Ed. In fact I could have condensed my version to a single line and avoided the intermediate variable, although it would have been a little longer and probably a little less clear.
I did want to use the same internal logic in both languages, though, so the differences would highlight blocks vs. alternative constructs in other languages.
August 26th, 2008 at 3:55 pm
There is a third point that becomes apparent with these examples (and one that is most compelling for me):
The ruby code reads in a very ‘natural’ way.
Excellent examples. I have bookmarked this blog in anticipation of the remainder of the series!
August 31st, 2008 at 12:40 pm
[...] metaclass ยป Blocks: Not Just Twisted Loops/Iterators, part 1 [...]