Rewriting Java in Scala & Making Code Lovely 2 – Functions as values

Ever wanted to filter a collection in Java and thought "this sucks"? Typically, you end up with code like:

List evens = new LinkedList();
for (Integer i : list) {
  if (i % 2 == 0) evens.add(i);
}
Or, if you're going for re-use:
Predicate evenNumbers = new Predicate() {
  @Override
  public boolean evaluate(Object o) {
    Integer n = (Integer)o; // Assume list contains only integers
    return n % 2 == 0;
  }
};
CollectionUtils.filter(list, evenNumbers);
Which is actually longer.

In Scala, functions can be values and this makes the above examples become very short:

val evens = list.filter(_ % 2 == 0)
You're not supposed to understand this yet - it's the bait to get you to read further ;-)

Defining Functions in Scala

Before we focus on this I'm going to quickly show you how functions are defined in Scala:
def getHelloString(username : String) : String = {
  "Hello " + username
}
This defines a function called getHelloString. It takes a single argument of type String called username and returns a String. Unlike Java, the type of an argument goes after the name. Likewise, the return type of a function goes after the argument list. It's also worth noting that there isn't a return statement here, the value of the last expression in the function is the return value.

That's all I'm going to say on functions for now - I'll come back to them in more detail in a later post.

Functions as Arguments

Let's take a look at the signature for the method filter:
def filter(p : (A) => Boolean) : Iterable[A]
This method takes an argument p and returns an Iterable[A] (the [A] is how you specify generics in Scala but we can ignore this for now).

The argument p is the interesting bit. The type of p is a function that takes an A (generics again) and returns a Boolean. To help this make sense here are a few more examples:

def a(p : String => Boolean) : Boolean
// The type of p is a function that takes a String and returns a Boolean

def b(p : String => List[String]) : Boolean // The type of p is a function that takes a String and returns a list of strings

def c(p : (String, Boolean) => Int) : Boolean // The type of p is a function that takes a String and a boolean and returns a list of strings

This is very different to Java. In Java you would define an interface and use that as the argument type (just like in the CollectionUtils example above).

Coming back to filter, we now know that it takes a single argument. That argument must be a function which takes a single argument of type A and returns a boolean. What is A? A is the same type as the type of the list we are filtering over. So:

List("A", "B").filter(...)
// requires a function that takes a String and returns a Boolean

List(1, 2).filter(...) // requires a function that takes an Int and returns a Boolean

With the knowledge we have so far we could comfortably write:
def isEven(n : Int) : Boolean = {
  n % 2 == 0
}

List(1, 2, 3, 4, 5, 6).filter(isEven)

And we'll get a list containing only the even numbers.

Function Literals

This is all very well and good, but it's still a lot to write - especially if we only ever use the isEven function in one place.

Fortunately, Scala offers a short-hand that allows us to write functions in-line:

List(1, 2, 3, 4, 5, 6).filter(n => n % 2 == 0)
The way this is written above is very similar to the argument types above. The argument passed to filter here is known as a function literal.

Placeholder Syntax

Scala gives us one further refinement to this:
List(1, 2, 3, 4, 5, 6).filter(_ % 2 == 0)
On first reading this can be cryptic. It's called placeholder syntax. The _ is a placeholder for the 1st argument to the function. When Scala sees this it replaces the _ with the 1st argument to the function. Each subsequent _ is a placeholder for the next argument. If we wrote:
dostuff(_ + _)
This would be the same as:
dostuff((a, b) => a + b)
It's important to note that when writing function literals with the arguments named parentheses must be used to group the arguments. This is to prevent ambiguity:
dostuff(a, b => a + b)
Could be read as:
dostuff(
  a,
  b => a + b)
Which isn't what we intended.

I learn best by example, so here are a few more examples of function literals.

Further Examples

Map

Map is one of the most useful functions in Scala's collections:

Map creates a new collection based on the original. Each element in the new collection is the result of applying the function given to each element in the original collection.

List(1, 2, 3, 4).map(_ * 2)
// Returns: List(2, 4, 6, 8)

List(1, 2, 3, 4).map(.toString) // Returns: List("1", "2", "3", "4")

findIndexOf

Finding the index of an element in a list is also very easy:
List(1, 2, 3, 4).findIndexOf( % 2 == 0)
// Finds the index of the first even number, or -1 if no such number exists

Count

How about counting the number of even numbers in a list?
List(1, 2, 3, 4).count(_ % 2 == 0)
// Returns 2

List("Hi", "Bye", "Hey", "Goodbye").count(_.length == 2) // Returns 1

What’s this given us?

More concise, more readable

Goodbye anonymous inner classes. If you've ever written something that fires events you'll know how horrible these can be.

Once I got used to passing functions around as arguments I soon realised how often I can re-use the same basic operations. Map and filter are two of the most used constructs in Java - I love that I can now write them in one line.

Easier to debug

The great thing about using functions as arguments is that you can write an algorithm once, and never again. I don't want to think about how many times I've written a basic loop in Java and forgotten to deal with the last case, off-by-one errors, etc.

Easier to test

Now that I can extract out algorithms more effectively I can test them to hell and back. After that, I only have to test that I'm calling them correctly - I don't have to retest all the corner cases all over again.

Summary

You should now understand:
  • Functions can be passed as arguments to other functions
  • Functions can be written in-line as literal functions
  • Literal functions can be extremely short when using placeholder syntax
If you don't, don't worry! If you've not come across functional programming before it can be quite a brain melting experience. My advice is to grab the Scala REPL and put in some of the examples above... then experiment and have fun!

Discussion

blog comments powered by Disqus

Colin Howe

I'm Colin. I like coding, ultimate frisbee and startups. I am VP of engineering at Conversocial