Anonymous Functions

Earlier in this book I showed that you can create a list of integers like this:

val ints = List(1,2,3)

When you want to create a larger list, you can also create them with the List class range method, like this:

val ints = List.range(1, 10)

That code creates ints as a list of integers whose values range from 1 to 10. You can see the result in the REPL:

scala> val ints = List.range(1, 10)
x: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)

In this lesson I’ll use lists like these to demonstrate a feature of functional programming known as anonymous functions. It will help to understand how these work before I demonstrate the most common Scala collections methods.

Examples

An anonymous function is like a little mini-function. For example, given a list like this:

val ints = List(1,2,3)

I can create a new list by doubling each element in ints, like this:

val doubledInts = ints.map(_ * 2)

This is what that example looks like in the REPL:

scala> val doubledInts = ints.map(_ * 2)
doubledInts: List[Int] = List(2, 4, 6)

As that shows, doubledInts is now the list, List(2, 4, 6). In this example, this code is an anonymous function:

_ * 2

In this example, this is the shorthand way of saying, “Multiply an element by 2.”

Once you’re comfortable with Scala, this is a common way to write anonymous functions, but you can also write them using longer forms. Besides writing that code like this:

val doubledInts = ints.map(_ * 2)

you can also write it like this:

val doubledInts = ints.map((i: Int) => i * 2)
val doubledInts = ints.map(i => i * 2)

All three lines have exactly the same meaning: Double each element in list to create a new list, doubledInts.

The _ character in Scala is something of a wildcard character. You’ll see it used in several different places. In this case it’s a shorthand way of saying, “An element from the list, list.”

Before I go too much further, I should also say that this map example is the equivalent of this Java code:

List<Integer> ints = new ArrayList<>(Arrays.asList(1, 2, 3));

// the `map` process
List<Integer> doubledInts = new ArrayList<Integer>();
for (int i: ints) {
    doubledInts.add(i * 2);
}

The map example shown is also the same as this Scala code:

val doubledInts = for (i <- ints) yield i * 2

Anonymous functions with the filter method

Another good way to show anonymous functions is with the filter method of the List class. Given this List again:

val ints = List.range(1, 10)

This is how you create a new list of all integers whose value is greater than 5:

val x = ints.filter(_ > 5)

This is how you create a new list whose values are all less than 5:

val x = ints.filter(_ < 5)

And as a little more complicated example, this is how you create a new list that contains only even values, by using the modulus operator:

val x = ints.filter(_ % 2 == 0)

If that’s a little confusing, remember that this example can also be written in these other ways:

val x = ints.filter((i: Int) => i % 2 == 0)
val x = ints.filter(i => i % 2 == 0)

This is what the previous examples look like in the REPL:

scala> val x = ints.filter(_ > 5)
x: List[Int] = List(6, 7, 8, 9)

scala> val x = ints.filter(_ < 5)
x: List[Int] = List(1, 2, 3, 4)

scala> val x = ints.filter(_ % 2 == 0)
x: List[Int] = List(2, 4, 6, 8)

Key points

The key points of this lesson are:

  • You can write anonymous functions as little snippets of code
  • You can use them with methods on the List class like map and filter
  • With these little snippets of code and powerful methods like those, you can create a lot of functionality with very little code

The Scala collections classes contain many methods like map and filter, and they’re a powerful way to create very expressive code.

Bonus: Digging a little deeper

You may be wondering how the map and filter examples work. The short answer is that when map is invoked on a list of integers — a List[Int] to be more precise — map expects to receive a function that transforms one Int value into another Int value. Because map expects a function (or method) that transforms one Int to another Int, this approach also works:

val ints = List(1,2,3)
def double(i: Int): Int = i * 2   //a method that doubles an Int
val doubledInts = ints.map(double)

The last two lines of that example are the same as this:

val doubledInts = ints.map(_ * 2)

Similarly, when called on a List[Int], the filter method expects to receive a function that takes an Int and returns a Boolean value. Therefore, given a method that’s defined like this:

def lessThanFive(i: Int): Boolean = if (i < 5) true else false

or more concisely, like this:

def lessThanFive(i: Int): Boolean = (i < 5)

this filter example:

val ints = List.range(1, 10)
val y = ints.filter(lessThanFive)

is the same as this example:

val y = ints.filter(_ < 5)

I write much more about the details behind this in both of my books, the Scala Cookbook, and Functional Programming, Simplified.

results matching ""

    No results matching ""