A higher-order function (HOF) is often defined as a function that (a) takes other functions as input parameters or (b) returns a function as a result. In Scala, HOFs are possible because functions are first-class values.
As an important note, while we use the common industry term “higher-order function” in this document, in Scala this phrase applies to both methods and functions. Thanks to Scala’s Eta Expansion technology, they can generally be used in the same places.
From consumer to creator
In the examples so far in this book you’ve seen how to be a consumer of methods that take other functions as input parameters, such as using HOFs like
In the next few sections you’ll see how to be a creator of HOFs, including:
- How to write methods that take functions as input parameters
- How to return a function from a method
In the process you’ll see:
- The syntax you use to define function input parameters
- How to call a function once you have a reference to it
As a beneficial side effect of this discussion, once you’re comfortable with this syntax, you’ll use it to define function parameters, anonymous functions, and function variables, and it also becomes easier to read the Scaladoc for higher-order functions.
Understanding filter’s Scaladoc
To understand how higher-order functions work, it helps to dig into an example.
For instance, you can understand the type of functions
filter accepts by looking at its Scaladoc.
filter definition in the
def filter(p: A => Boolean): List[A]
This states that
filter is a method that takes a function parameter named
p stands for a predicate, which is just a function that returns a
filter takes a predicate
p as an input parameter, and returns a
A is the type held in the list; if you call
filter on a
A is the type
At this point, if you don’t know the purpose of the
filter method, all you’d know is that its algorithm somehow uses the predicate
p to create and return the
Looking specifically at the function parameter
p, this part of
p: A => Boolean
means that whatever function you pass in must take the type
A as an input parameter and return a
So if your list is a
List[Int], you can replace the generic type
Int, and read that signature like this:
p: Int => Boolean
isEven has this type—it transforms an input
Int into a resulting
Boolean—it can be used with
Writing methods that take function parameters
Given that background, let’s start writing methods that take functions as input parameters.
Note: To make the following discussion clear, we’ll refer to the code you’re writing as a method, and the code you’re accepting as an input parameter as a function.
A first example
To create a method that takes a function parameter, all you have to do is:
- In your method’s parameter list, define the signature of the function you want to accept
- Use that function inside your method
To demonstrate this, here’s a method that that takes an input parameter named
f is a function:
def sayHello(f: () => Unit): Unit = f()
This portion of the code—the type signature—states that
f is a function, and defines the types of functions the
sayHello method will accept:
f: () => Unit
Here’s how this works:
fis the name of the function input parameter. It’s just like naming a
- The type signature of
fspecifies the type of the functions this method will accept.
f’s signature (on the left side of the
=>symbol) states that
ftakes no input parameters.
Unitportion of the signature (on the right side of the
=>symbol) indicates that
fshould not return a meaningful result.
- Looking back at the body of the
sayHellomethod (on the right side of the
f()statement there invokes the function that’s passed in.
Now that we’ve defined
sayHello, let’s create a function to match
f’s signature so we can test it.
The following function takes no input parameters and returns nothing, so it matches
f’s type signature:
def helloJoe(): Unit = println("Hello, Joe")
Because the type signatures match, you can pass
sayHello(helloJoe) // prints "Hello, Joe"
If you’ve never done this before, congratulations:
You just defined a method named
sayHello that takes a function as an input parameter, and then invokes that function in its method body.
sayHello can take many functions
It’s important to know that the beauty of this approach is not that
sayHello can take one function as an input parameter; the beauty is that it can take any function that matches
For instance, because this next function takes no input parameters and returns nothing, it also works with
def bonjourJulien(): Unit = println("Bonjour, Julien")
Here it is in the REPL:
scala> sayHello(bonjourJulien) Bonjour, Julien
This is a good start. The only thing to do now is see a few more examples of how to define different type signatures for function parameters.
The general syntax for defining function input parameters
In this method:
def sayHello(f: () => Unit): Unit
We noted that the type signature for
() => Unit
We know that this means, “a function that takes no input parameters and returns nothing meaningful (given by
To demonstrate more type signature examples, here’s a function that takes a
String parameter and returns an
f: String => Int
What kinds of functions take a string and return an integer? Functions like “string length” and checksum are two examples.
Similarly, this function takes two
Int parameters and returns an
f: (Int, Int) => Int
Can you imagine what sort of functions match that signature?
The answer is that any function that takes two
Int input parameters and returns an
Int matches that signature, so all of these “functions” (methods, really) are a match:
def add(a: Int, b: Int): Int = a + b def subtract(a: Int, b: Int): Int = a - b def multiply(a: Int, b: Int): Int = a * b
As you can infer from these examples, the general syntax for defining function parameter type signatures is:
variableName: (parameterTypes ...) => returnType
Because functional programming is like creating and combining a series of algebraic equations, it’s common to think about types a lot when designing functions and applications. You might say that you “think in types.”
Taking a function parameter along with other parameters
For HOFs to be really useful, they also need some data to work on.
For a class like
map method already has data to work on: the data in the
But for a standalone HOF that doesn’t have its own data, it should also accept data as other input parameters.
For instance, here’s a method named
executeNTimes that has two input parameters: a function, and an
def executeNTimes(f: () => Unit, n: Int): Unit = for (i <- 1 to n) f()
def executeNTimes(f: () => Unit, n: Int): Unit = for i <- 1 to n do f()
As the code shows,
executeNTimes executes the
Because a simple
for loop like this has no return value,
executeNTimes, define a method that matches
// a method of type `() => Unit` def helloWorld(): Unit = println("Hello, world")
Then pass that method into
executeNTimes along with an
scala> executeNTimes(helloWorld, 3) Hello, world Hello, world Hello, world
executeNTimes method executes the
helloWorld function three times.
As many parameters as needed
Your methods can continue to get as complicated as necessary.
For example, this method takes a function of type
(Int, Int) => Int, along with two input parameters:
def executeAndPrint(f: (Int, Int) => Int, i: Int, j: Int): Unit = println(f(i, j))
multiply methods match that type signature, they can be passed into
executeAndPrint along with two
def sum(x: Int, y: Int) = x + y def multiply(x: Int, y: Int) = x * y executeAndPrint(sum, 3, 11) // prints 14 executeAndPrint(multiply, 3, 9) // prints 27
Function type signature consistency
A great thing about learning about Scala’s function type signatures is that the syntax you use to define function input parameters is the same syntax you use to write function literals.
For instance, if you were to write a function that calculates the sum of two integers, you’d write it like this:
val f: (Int, Int) => Int = (a, b) => a + b
That code consists of the type signature:
val f: (Int, Int) => Int = (a, b) => a + b -----------------
The input parameters:
val f: (Int, Int) => Int = (a, b) => a + b ------
and the body of the function:
val f: (Int, Int) => Int = (a, b) => a + b -----
Scala’s consistency is shown here, where this function type:
val f: (Int, Int) => Int = (a, b) => a + b -----------------
is the same as the type signature you use to define a function input parameter:
def executeAndPrint(f: (Int, Int) => Int, ... -----------------
Once you’re comfortable with this syntax, you’ll use it to define function parameters, anonymous functions, and function variables, and it becomes easier to read the Scaladoc for higher-order functions.