Pattern 1Replacing Functional Interface

Intent

To encapsulate a bit of program logic so that it can be passed around, stored in data structures, and generally treated like any other first-class construct

Overview

Functional Interface is a basic object-oriented design pattern. It consists of an interface with a single method with a name like run, execute, perform, apply, or some other generic verb. Implementations of Functional Interface perform a single well-defined action, as any method should.

Functional Interface lets us call an object as if it were a function, which lets us pass verbs around our program rather than nouns. This turns the traditional object-oriented view of the world on its head a bit. In the strict object-oriented view, objects, which are nouns, are king. Verbs, or methods, are second-class citizens, always attached to an object, doomed to a life of servitude to their noun overlords.

Also Known As

Function Object
Functoid
Functor

Functional Replacement

A strict view of object orientation makes some problems clumsier to solve. I’ve lost track of the number of times I’ve written five or six lines of boilerplate to wrap a single line of useful code into Runnable or Callable, two of Java’s most popular instances of Functional Interface.

To simplify things, we can replace Functional Interface with plain functions. It might seem strange that we can replace an object with seemingly more primitive functions, but functions in functional programming are much more powerful than functions in C or methods in Java.

In functional languages, functions are higher order: they can be returned from functions and used as arguments to others. They are first-class constructs, which means that in addition to being higher order they can also be assigned to variables, put into data structures, and generally manipulated. They can be unnamed, or anonymous functions, which are extremely handy for small, one-off pieces of code. In fact, Functional Interface (as its name might suggest) is a pattern that in the object-oriented world approximates the behavior of the functions of the functional world.

We’ll cover a couple of different flavors of a Functional Interface replacement in this section. The first replaces smaller instances of the pattern—say ones that take a few lines of code—with an anonymous function. This is similar to using an anonymous inner class to implement Functional Interface in Java and is covered in Sample Code: Anonymous Functions.

The second covers instances of the pattern that span more than a few lines. In Java, we’d implement these using a named rather than an anonymous class; in the functional world we use a named function, as we do in Sample Code: Named Functions.

Sample Code: Anonymous Functions

Our first example demonstrates anonymous functions and how we can use them to replace small instances of Functional Interface. One common situation where we’d do this is when we need to sort a collection differently than its natural ordering, the way it’s commonly ordered.

To do so, we need to create a custom comparison so that the sorting algorithm knows which elements come first. In classic Java, we need to create a Comparator implemented as an anonymous class. In Scala and Clojure, we get right to the point by using an anonymous function. We’ll take a look at a simple example of sorting differently than the natural ordering for an object: sorting a Person by first name rather than last.

Classic Java

In classic Java, we’ll use a Functional Interface named Comparator to help with our sort. We’ll implement it as an anonymous function, since it’s only a tiny bit of code, and we’ll pass it into the sorting function. The kernel of the solution is here:

JavaExamples/src/main/java/com/mblinn/mbfpp/oo/fi/PersonFirstNameSort.java
 
Collections​.sort(people, ​new​ ​Comparator​<Person>() {
 
public​ ​int​ compare(Person p1, Person p2) {
 
return​ p1.getFirstName().compareTo(p2.getFirstName());
 
}
 
});

This works, but most of the code is extra syntax to wrap our one line of actual logic into an anonymous class. Let’s see how anonymous functions can help clean this up.

In Scala

Let’s take a look at how we’d solve the problem of sorting by first rather than last name in Scala. We’ll use a case class to represent people, and we’ll do away with the Functional Interface Comparator. In its place, we’ll use a plain old function.

Creating an anonymous function in Scala uses the following syntax:

 
(arg1: Type1, arg2: Type2) => FunctionBody

For instance, the following REPL session creates an anonymous function that takes two integer arguments and adds them together.

 
scala>​ (int1: Int, int2: Int) => int1 + int2
 
res0: (Int, Int) => Int = <function2>

Now that we’ve got the basic syntax down, let’s see how to use an anonymous function to solve our person-sorting problem. To do so we use a method in Scala’s collections library, sortWith. The sortWith method takes a comparison function and uses it to help sort a collection, much like Collections.sort takes a Comparator to do the same.

Let’s start with the code for our Person case class:

ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/fi/PersonExample.scala
 
case​ ​class​ Person(firstName: ​String​, lastName: ​String​)

Here’s a vector full of them to use for test data:

ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/fi/PersonExample.scala
 
val​ p1 = Person(​"Michael"​, ​"Bevilacqua"​)
 
val​ p2 = Person(​"Pedro"​, ​"Vasquez"​)
 
val​ p3 = Person(​"Robert"​, ​"Aarons"​)
 
 
val​ people = Vector(p3, p2, p1)

The sortWith method expects its comparison function to return a Boolean value that tells it whether the first argument is higher than the second argument. Scala’s comparison operators < and > work on strings, so we can use them for this purpose.

The following code demonstrates this approach. We can omit the type annotations for the function parameters. Scala is able to infer them from the sortWith method:

ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/fi/PersonExample.scala
 
people.sortWith((p1, p2) => p1.firstName < p2.firstName)

Running this in Scala’s REPL gets us the following output.

 
res1: scala.collection.immutable.Vector[...] =
 
Vector(
 
Person(Michael,Bevilacqua),
 
Person(Pedro,Vasquez),
 
Person(Robert,Aarons))

This is shorter and simpler than using an equivalent implementation of Functional Interface!

In Clojure

We define an anonymous function in Clojure using the fn special form, as the following code outline shows.

 
(​fn​ [arg1 arg2] function-body)

Let’s start by creating some test people. In Clojure, we won’t define a class to carry around data; we’ll use a humble map:

ClojureExamples/src/mbfpp/rso/person.clj
 
(​def​ p1 {:first-name ​"Michael"​ :last-name ​"Bevilacqua"​})
 
(​def​ p2 {:first-name ​"Pedro"​ :last-name ​"Vasquez"​})
 
(​def​ p3 {:first-name ​"Robert"​ :last-name ​"Aarons"​})
 
 
(​def​ people [p3 p2 p1])

Now we create an anonymous ordering function and pass it into the sort function along with the people we want to sort, as the following code demonstrates:

 
=> (sort (fn [p1 p2] (compare (p1 :first-name) (p2 :first-name))) people)
 
({:last-name "Bevilacqua", :first-name "Michael"}
 
{:last-name "Vasquez", :first-name "Pedro"}
 
{:last-name "Aarons", :first-name "Robert"})

By eliminating the extra syntax we need in Java to wrap our ordering function in a Comparator, we write code that gets right to the point.

Sample Code: Named Functions

Let’s expand our person-sorting problem a bit. We’ll add a middle name to each person in our list and modify our unusual sorting algorithm to sort by first name, then last name if the first names are the same, and finally middle name if the last names are also the same.

This makes the comparison code long enough that we should no longer embed it in the code that’s using it. In Java we move the code out of an anonymous inner class and into a named class. In Clojure and Scala, we move it into a named function.

Classic Java

Anonymous classes and functions are great when the logic they’re wrapping is small, but when it grows larger it gets messy to embed. In classic Java, we move to using a named class, as is sketched out below:

 
public​ ​class​ ComplicatedNameComparator ​implements​ ​Comparator​<Person> {
 
public​ ​int​ compare(Person p1, Person p2) {
 
complicatedSortLogic
 
}
 
}

With higher-order functions, we use a named function.

In Scala

We start off by expanding our Scala case class to have a middle name and by defining some test data:

ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/fi/PersonExpanded.scala
 
case​ ​class​ Person(firstName: ​String​, middleName: ​String​, lastName: ​String​)
 
val​ p1 = Person(​"Aaron"​, ​"Jeffrey"​, ​"Smith"​)
 
val​ p2 = Person(​"Aaron"​, ​"Bailey"​, ​"Zanthar"​)
 
val​ p3 = Person(​"Brian"​, ​"Adams"​, ​"Smith"​)
 
val​ people = Vector(p1, p2, p3)

Now we create a named comparison function and pass it into sortWith, as the following code demonstrates:

ScalaExamples/src/main/scala/com/mblinn/mbfpp/oo/fi/PersonExpanded.scala
 
def​ complicatedSort(p1: Person, p2: Person) =
 
if​ (p1.firstName != p2.firstName)
 
p1.firstName < p2.firstName
 
else​ ​if​ (p1.lastName != p2.lastName)
 
p1.lastName < p2.lastName
 
else
 
p1.middleName < p2.middleName

And voilà! We can easily sort our people using an arbitrarily named function:

 
scala>​ people.sortWith(complicatedSort)
 
res0: scala.collection.immutable.Vector[...] =
 
Vector(
 
Person(Aaron,Jeffrey,Smith),
 
Person(Aaron,Bailey,Zanthar),
 
Person(Brian,Adams,Smith))

In Clojure

The Clojure solution is quite similar to the Scala one. We’ll need a named function that can compare people according to our more complex set of rules, and we’ll need to add middle names to our people.

Here’s the code for our complicated sort algorithm:

ClojureExamples/src/mbfpp/rso/person_expanded.clj
 
(​defn​ complicated-sort [p1 p2]
 
(​let​ [first-name-compare (​compare​ (p1 :first-name) (p2 :first-name))
 
middle-name-compare (​compare​ (p1 :middle-name) (p2 :middle-name))
 
last-name-compare (​compare​ (p1 :last-name) (p2 :last-name))]
 
(​cond
 
(​not​ (​=​ 0 first-name-compare)) first-name-compare
 
(​not​ (​=​ 0 last-name-compare)) last-name-compare
 
:else middle-name-compare)))

Now we can call sort as before, but instead of passing in an anonymous function, we pass the named function complicated-sort:

ClojureExamples/src/mbfpp/rso/person_expanded.clj
 
(​def​ p1 {:first-name ​"Aaron"​ :middle-name ​"Jeffrey"​ :last-name ​"Smith"​})
 
(​def​ p2 {:first-name ​"Aaron"​ :middle-name ​"Bailey"​ :last-name ​"Zanthar"​})
 
(​def​ p3 {:first-name ​"Brian"​ :middle-name ​"Adams"​ :last-name ​"Smith"​})
 
(​def​ people [p1 p2 p3])
 
=> (sort complicated-sort people)
 
({:middle-name "Jeffrey", :last-name "Smith", :first-name "Aaron"}
 
{:middle-name "Bailey", :last-name "Zanthar", :first-name "Aaron"}
 
{:middle-name "Adams", :last-name "Smith", :first-name "Brian"})

That’s all there is to it.

Discussion

Functional Interface is a bit odd. It comes from Java’s current insistence on turning everything into an object, a noun. This is like having to use a ShoePutterOnner, a DoorOpener, and a Runner just to go for a run! Replacing the pattern with higher-order functions helps us in several ways. The first is that it reduces the syntactic overhead of many common tasks, cruft you have to write in order to write the code you want to write.

For instance, the first Comparator we came across in this section required five lines of Java code (formatted properly) to convey just one line of actual computation:

 
new​ ​Comparator​<Person>() {
 
public​ ​int​ compare(Person left, Person right) {
 
return​ left.getFirstName().compareTo(right.getFirstName());
 
}
 
}

Compare that to a single line of Clojure.

 
(​fn​ [left right] (​compare​ (left :first-name) (right :first-name)))

More importantly, using higher-order functions gives us a consistent way of passing around small bits of computation. With Functional Interface, you need to look up the right interface for every little problem you want to solve and figure out how to use it. We’ve seen Comparator in this chapter and mentioned a few other common uses of the pattern. Hundreds of others exist in Java’s standard libraries and other popular libraries, each as unique as a snowflake, but more annoyingly different than beautiful.

Functional Interface and its replacements in this chapter have some differences that don’t touch on the core problem that it’s meant to solve. Since Functional Interface is implemented with a class, it defines a type and can use common object-oriented features such as subclassing and polymorphism. Higher-order functions cannot. This is actually a strength of higher-order functions over Functional Interface: you don’t need a new type for each type of Functional Interface when just the existing function types will do.

For Further Reading

Effective Java [Blo08]Item 21: Use Function Objects to Represent Strategies

JSR 335: Lambda Expressions for the Java Programming Language [Goe12] [2]

Related Patterns

Pattern 3, Replacing Command

Pattern 6, Replacing Template Method

Pattern 7, Replacing Strategy

Pattern 16, Function Builder

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.35.194