On Monday evening, I did a bit of Ruby hacking. On Tuesday morning, I arrived at work and my first task involved iterating through a list, and doing something to each of its elements. My fingers were already typing list.each, and I had to wrench my brain from my Ruby mindset back to the Java Way.
Some time later in the day, I read Philip Greenspun's much-linked Java-is-an-SUV flame.
Problem: I have a list of objects. I want to create another list containing the ‘id’ property of those objects.
Solutions:1
- Ruby
list.map { |i| i.id }- Perl
map { $_->id } @list;- Python
[x.id for x in list]- Common Lisp (Corrected by Andreas)
(mapcar #'id list)- Smalltalk (from James)
list collect: [:each | each id]-
- OGNL (from Jason)
list.{id}- Java
List ids = new ArrayList(); for (Iterator i = list.iterator(); i.hasNext(); ) { ids.add(new Long(((Thingy)i.next()).getId())); }- Java w/ 1.5-style Generics/For Loop/Autoboxing
List<Long> ids = new ArrayList<Long>(); for (Thingy x :list) ids.add(x.getId());
Collection ids = CollectionUtils.collect(
x, new Transformer() {
public Object transform( Object thingy ) {
return ((Thingy)thingy).getId();
}
});1 I was too lazy to test that they work, but the syntax is close enough. Bug reports to /dev/null.
And with Generics, "ForEach" Loop and autoboxing you'd have:
List ids = new ArrayList();
for (Thingy x :list)
ids.add(x.getId());
For readability sake (meaning, I can look at the code and see what it is doing), I'd pick Java, followed (not so closely) by Perl. Of course, this may reflect the fact that I am not familiar with the other languages....
In OGNL:
list.{id}
Python's list comprehensions rock, there is no doubt about it.
Since Java has so many libraries (like commons-collections) TMTOWDI (apologies for formatting):
Collection ids = CollectionUtils.collect(
x, new Transformer() {
public Object transform( Object thingy ) {
return ((Thingy)thingy).getId();
}
} );
BTW, I think it's important to remember that judging readability is fairly worthless if you don't know the languages involved: 'map' is a common Perl idiom which, if you knew Perl, you'd certainly know. I'd hazard a guess that most of us don't maintain code in a language we don't know :-)
The Common Lisp code portion can even be shorter (and less irritatingly lambda-ridden (-:):
(mapcar #'id list)
The conclusions I draw:
#1: bring on 1.5!
#2: (obviously) the one you know is easiest to understand.
Personally the 1.5 example seems to read easier than all except the Smalltalk and maybe the Python examples. But I'm biased of course, as per #2.
With F-Script:
list id
In C++, assuming that 'mylist' is the list you are working on, and the elements are instances of 'myobj':
list<long> ids;
for (list<myobj>::const_iterator it = mylist.begin(); it != mylist.end(); ++ it)
ids.push_back(it->id);
Or if it's a list of pointers to instances of myobj:
list<long> ids;
for (list<myobj*>::const_iterator it = mylist.begin(); it != mylist.end(); ++ it)
ids.push_back((*it)->id);
It's hardly fair to compare statically-typed Java with a bunch of dynamically-typed languages. So how about adding a haskell version:
map thingy_id list
The only "fair" comparison here is with C++. Java was designed to be a better C++, and it is.
list.map( Thingy each => { return each.id; } );
Nice (statically-checked, JVM bytecodes, uses Java libraries) http://nice.sourceforge.net/index.html
In the Python example, 'list' need not actually be a materialised list but can be any iterable, so allowing the source items to be obtained lazily; with appropriate type declarations, the same could be true of the various Java examples. Nevertheless what is *produced* by the Python and Java versions given here will still be a materialised list. Python 2.4 will introduce generator expressions, which combine the ideas of generators and list comprehensions, so allowing the transformation of one iterable into another without materialising a list:
(x.id for x in list)
I imagine that the Java equivalent of this would be even longer than the examples given before.