Monday, October 12, 2009

Story Time with Google Collections

Note: this post in no way suggests that Google Collections is wasteful. Quite the opposite, it's spectacularly awesome. If you haven't used it yet, go get it, play with it, discover appropriate uses of its power, and go rock your project.

Several months ago there was a discussion on one of the mailing lists at work about preference of part of the Google Collections API over an imperative equivalent. Specifically, using Iterables.transform to translate a List<X> into List<Y> by creating a Function that translates X into Y. In other words, which was better?

This:
Function<X, Y> function = new Function<X, Y>() {
  public Y apply(X from) {
    return X.asY();
  }
};
List<Y> listOfY = Lists.newArrayList(
    Iterables.transform(listOfX, function));

Or this:
List<Y> listOfY = Lists.newArrayList();
for(X x : listOfX) {
  listOfY.add(x.asY());
}

There was this argument about the value of the functional style over the imperative style, and frankly, I found it all rather confusing. All things being equal, the former was just too damn much. And that's the key here: all things being equal. If we were using a language with proper closures, or if there was a host of Function instances available to substitute for function, I might have (and have had) a different opinion.

To illustrate my point, I submitted this story to the mailing list.
Me: Kevin, tell me a story.
Kevin: Seriously. Go away.
Me: TELL!!
Kevin: OK. Once upon a time there was a programmer. He wanted a List. of Strings. But unfortunately, he had a List of Another Type.
Me: What type?
Kevin: Doesn't matter.
Me: WHAT TYYYYYPE!
Kevin: DOESN'T. MATTER.
Me: Ice Cream?
Kevin: Ice Cream. Fine Ice Cream. So the programmer...
Me: How much ice cream?
Kevin: What?
Me: How MUCH?
Kevin: Well, let me put it this way: if it was a List of ice cream, it would be A LOT of ice cream.
Me: Eeeeeeee!
Kevin: (pause) So... this programmer decided to take the list and return a New List. He decided to transform his list by creating a magical function that converts Another Type ... I mean ... _ice cream_ into Strings. And that Function has a method called 'apply'. Apply takes things of type ... sigh ... _ice cream_ and converts it to strings. It does this by calling its callStringMethod. The End.
Me: Wha?

--- REWIND
Kevin ... it would be A LOT of ice cream.
Me: Eeeeeeee!
Kevin: (pause) So... this programmer created a new, empty list. A place to store the Strings. And then he went through every _ice cream_ element in the old list, called its callString Method, and added it to the new list. And then he returned that list. The end.
Me: *sob* that is so awesome. What happened to the ice cream?
Kevin: I think Josh has it. Hurry up before he eats it.
Nobody here is saying that functional programming is bad or wrong. But sometimes it's not really all that awesome to be cutting edge.

Update: What I mean to say, that my colleague said in much fewer words, is that the primary concern in coding should be making your intention as plain as possible.

4 comments:

Mike Bostock said...

I use Array.map all the time in JavaScript. However, JavaScript has closures (and function expressions) so you can take the declarative/functional approach without it obscuring your intent as much.

In this case: `array.map(function(s) s.asY())`.

Of course, this creates a copy of the array rather than a view. But it's quite succinct and easy, if you don't care about the difference in performance.

konberg said...

If the closure syntax was available then the readability improvement would surely be worth the potentially small performance penalty, and the story wouldn't be as confusing.

Brian said...

/me goes off to cry about the lack of closures in Java....

shadowmatter said...

I know I'm way late to this conversation, but... technically, it's not closures you need. The applied function doesn't need access to any elements outside the function, which is the binding a closure provides. You just need a function declaration syntax that doesn't suck. Something like "lambda x: return x.asY()" in Python, or "function(x) { return x.asY(); }" in Javascript. Being dynamically typed seems to lend a lot of brevity here.