Friday, January 29, 2010

Why Your Company Should Let You Use Scala at Work

Java at Work: An Increasingly Frustrating Experience

A colleague and I were writing a test in Java today and I had one of those "I can't believe it takes this much work to write this stupid function" moments. If you do any Scala programming but still do a lot of Java programming, you'll know exactly what I mean. I'll walk you through a mock of what we were doing and you can see why Scala just pummels Java when it comes to expressiveness. (Early warning: do not use the word "expressive" when trying to convince your manager to let you use Scala at work.)

So, here's the gist of the code that we were testing:

import java.util.ArrayList;
import java.util.List;

class AccountSummaryService {
public List<CustomerSummaryDTO> getAccountSummaries() {
return new ArrayList<CustomerSummaryDTO>();
}
}

class CustomerSummaryDTO {
private String name;
private List<AccountSummaryDTO> accountSummaries;

public String getName() {
return name;
}

public List<AccountSummaryDTO> getAccounts() {
return accountSummaries;
}
}

class AccountSummaryDTO {
private String name;
private int balance;

public String getName() {
return name;
}

public int getBalance() {
return balance;
}
}
and here's the gist of the test that we wrote:

import org.junit.Test;

import java.util.List;
import java.util.NoSuchElementException;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

class AccountSummaryServiceJavaTest {
AccountSummaryService service = new AccountSummaryService();

@Test
public void getAccountSummaries() {
List<CustomerSummaryDTO> result = service.getAccountSummaries();
assertThat(balanceFor("Customer 1", "Account #2", result),
is(1000000));
}

private int balanceFor(String customerName, String accountName,
List<CustomerSummaryDTO> result) {
for (CustomerSummaryDTO customer : result) {
if (customer.getName().equals(customerName)) {
for (AccountSummaryDTO account : customer.getAccounts()) {
if (account.getName().equals(accountName)) {
return account.getBalance();
}
}
}
}
throw new NoSuchElementException("Account not found");
}
}
As you can see, I'm a bit of a fan of fluent interfaces, hence the use of Hamcrest matchers and the balanceFor() method. This makes for a nice, readable test, but boy is that balanceFor() method ugly! Considering the simple job it's doing, there is a lot of code and - the thing I especially hate - a lot of noise. I would say this code has a relatively low signal-to-noise ratio.

The lack of visual appeal isn't the only problem. Another problem is that, despite being trivial, it took more than a few seconds to write. An even more disturbing problem is that my colleague and I both spent another 30 seconds triple-checking this code to make sure it was doing what we wanted it to. Not because it's complex (it's not) or because we're stupid (we're not). We were checking it over because (a) it took too long to write for such a simple operation, so we thought maybe it could be simplified, and (b) there is so much noise in the code that it just LOOKED like a bear trap for stupid errors.

I quipped that the same function in Scala would be much shorter and easier to read. My colleague called me on it and asked what the Scala would look like, so I wrote some hacky Scala in a comment to give him the feel of it. It was, of course, slightly wrong, and turned out to be more complex than it needed to be, but I wrote it again when I got home and compiled it just to prove my point. Here's the balanceFor() method rewritten in Scala:

def balanceFor(customerName : String, accountName : String, result :
Iterable[CustomerSummaryDTO]) =
result.find(_.getName == customerName).get
.getAccounts().find(_.getName == accountName).get
.getBalance
You can see straight away that this function is shorter. Just syntactically, using Scala has removed a lot of the noise: theres no type declarations in the implementation; there's no verbose but meaningless local variable names because we can use _ to create anonymous functions; we're able to use == instead of .equals() with the same effect; the exception is thrown by the API because find() returns an Option; and, thanks to the power of Scala's API, we can even lose the braces because the whole function is just one expression.

But its endearing qualities don't end at the reduced demands on screen lines and hard drive. The Scala implementation reads in a way that is so much closer to the intent of the function - so much closer than a for loop with a nested if, with a nested for, with a nested if, followed by four closing braces and a throw statement. The find() function expresses what we are trying to do exactly; to a reader attempting to comprehend the code, its simplicity is profound when compared to the for statement and if statement that combined do the same thing but are spread over two lines in the Java version.

To make a fair comparison, I will highlight that Scala has added one piece of noise to the implementation that wasn't present in the Java: the .get call at the end of each of the first two lines, used to access the contents of the Option returned by find(). To anyone who has read an introductory Scala book, this will make immediate sense and to anyone that hasn't even seen Scala before, a 30-second explanation of Option should make everything pretty clear.

Note: Some people believe that you should never use the .get function on Option - never, ever! I don't agree with them absolutely. In general, this function is probably not a good one to call if you're writing production Scala code, but I think it can still be useful in certain circumstances. See the comments if you want to know more.


For me, the best part about this code is that the combination of its succinctness, expressiveness and almost total lack of noise means that we wouldn't need to spend 30 seconds triple-checking it. Not only did it take such a short time to write that I could still remember the first key-stroke by the time I'd finished, but I can look at this code and see what it does without having to read it.

Hang On, I Thought This Blog Was About Using Scala at Work?

Actually, so far it's been about not using Scala at work, and how annoying that can be.
How can we change this situation?

Well, imagine you go to your boss and you say, "Hey, Boss. Can we start using Scala?" and he says, "Why?" and you say, "Um, 'cause it's a functional language so it's more expressive and it has monads and everything's immutable so it might help us to write better multi-threaded code" and he'll say? ... "Get back to work".

Well, the silly little method above, along with the issues it solved, is a perfect little use case for why Scala could be great for your company's software development. Forget all the functional paradigms and monadic, higher-kinded academia - these are just buzz words that dumb people use to sound important. (Kidding.) No, there are real, practical, business reasons your company should let you code in Scala. My experience, as demonstrated in the code here, is that Scala lets me write code faster and also makes it harder for me to get things wrong. Or, to rephrase that in managerese, it increases output and improves quality.

So, instead of pitching technical buzzwords at your boss, imagine trying this out: "Hey, Boss. How would you like it if I could write twice as much code in a day and have more confidence that it actually does what I intend it to?" Now you're speaking his language: Output and Quality. But why not go one better than imagining? Why not go and ask your boss now!

If your boss says YES, and you need to get up to speed with Scala, you'll probably want to grab one of these books:

From Amazon




From The Book Depository


Programming in Scala - Martin Odersky, Lex Spoon & Bill Venners (Artima)

Programming Scala - Venkat Subramaniam (The Pragmatic Programmers)

Programming Scala - Dean Wampler & Alex Payne (O'Reilly)

Beginner Scala - David Pollak (Apress)

Pro Scala - Gregory Meredith (Apress)


Disclaimer:
It's not my intention to represent Scala as being better than Java for every project in every organisation, or for any particular one at all. Obviously there are many more issues to consider when adopting a language than just those presented here. My claims that Scala increases output and quality are based only on my own anecdotal observations and not on any empirical evidence. You should make your own investigations before making any commitment to using Scala in a commercial context.

7 comments:

  1. Thanks for your blog posts!

    Very, very picky comment on this post. I may have missed the point but I believe you mean _low_ signal to noise ratio. From the Wikipedia link you provide "The higher the ratio, the less obtrusive the background noise is"

    ReplyDelete
  2. You're right, Trent. Thanks for picking that up. It was my frustration that was high; the signal is definitely weak.
    Cheers, Graham.

    ReplyDelete
  3. You can get rid of the ugly .get by using flatMap

    def balanceFor(customerName : String,
    accountName : String,
    result : Iterable[CustomerSummaryDTO])
    : Option[Double]
    = result.find(_.getName == customerName)
    .flatMap(_.getAccounts().find(
    _.getName == accountName)
    .flatMap(_.getBalance)

    ReplyDelete
  4. oxbow,

    Thanks for your suggestion regarding alternative ways to handle an Option. This post is obviously not about the best ways to use Option but about how Java translated directly into Scala has great advantages.

    For what it's worth, in your suggestion you have changed the contract of the method, which was originally to return an int or throw an exception. However, adding a simple ".get" on the end of your code would fix this. ;)

    ReplyDelete
  5. Clearly you're not looking for a code critique, but it's such bad advice to the unwary reader one might feel a moral duty to point out that calling ".get" is almost invariably the wrong thing. Certainly it is here.

    def balanceFor(customerName : String, accountName : String, result : Iterable[CustomerSummaryDTO]) = {
    for (customer <- result find (_.getName == customerName) ; account <- customer.getAccounts find (_.getName == accountName)) yield
    account.getBalance
    } getOrElse Predef.error("at least throw a meaningful exception, unless you also signal failures with NPEs")

    ReplyDelete
  6. @extempore: You're right. If you're writing functional code and utilising the Option class, calling .get is almost certainly a Bad Idea.

    However, the subject of the blog is to translate a Java method into Scala, the contract of which was "either return an int or throw a NoSuchElementException". This is precisely the implementation of .get on an Option[Int]. (It doesn't throw an NPE, as you suggest.) So when you say that calling .get is "the wrong thing" to do here, I can't agree with you - the code satisfies the contract.

    I take your point about unwary novices potentially garnering bad advice about Option, though I doubt any Scala programmer would gain a significant chunk of their knowledge about Option from this blog. For what it's worth, I've added a note to the blog to warn people that they may endure scorn if they admit to using get. ;)

    I think it's worth pointing out that the code example we're discussing is from a test class. Test code is written with different requirements to production code, in particular when it comes to how a failed assumption (e.g. "this Option should always be a Some") is handled.

    ReplyDelete
  7. I love Scala as well, and I have exactly the same feeling.

    Anyway when you have to stuck to Java you could do something very similar by using the lambdaj library. For example you could rewrite your balanceFor() method and get rid of all cycles and conditions with hamcrest matchers and lambdaj as it follows:

    private int balanceFor(String customerName, String accountName, List result) {
    return selectUnique(collect(forEach(
    select(result, having(on(CustomerSummaryDTO.class).getName(), equalTo(customerName)))
    ).getAccounts())), having(on(AccountSummaryDTO.class).getName(), equalTo(customerName))).getBalance();
    }

    Not as readable as in Scala, but far better than the plain java version. Don't you think so

    My 2 cents,
    Mario Fusco
    twitter.com/mariofusco

    ReplyDelete