How to Write Scala Functions That Take Functions as Input Parameters

Motivation and Goals

The topic I’m about to cover is a big part of functional programming: Power programming that’s made possible by passing functions to other functions to get work done.

So far I’ve shown how to be the consumer of functions that take other functions as input parameters, that is, the consumer of Higher-Order Functions (HOFs) like map and filter. In this lesson I’m going to show everything you need to know to be the producer of HOFs, i.e., the writer of HOF APIs (how to write functions like map and filter).

Therefore, the primary goal of this lesson is to show how to write functions that take other functions as input parameters. I’ll show:

  • The syntax you use to define function input parameters
  • Multiple examples of that syntax
  • How to execute a function once you have a reference to it

As a beneficial side effect of this lesson, you’ll also learn to read the source code and Scaladoc for other HOFs, and you’ll be able to understand the function signatures they’re looking for.

Terminology

Before we start, here are a few notes about the terminology I’ll use in this lesson.

  1. I use the acronym “FIP” to stand for “function input parameter.” This isn’t an industry standard, but because I use the term so often, I think the acronym makes the text easier to read.

  2. As shown already, I’ll use “HOF” to refer to “Higher-Order Function.”

  3. As shown in the previous lessons, you can create functions as variables, and because of Eta Expansion you can do that by writing them as either (a) val functions or (b) def methods. Because of this, and because I think def methods are easier to read, from now on I’ll write def methods and refer to them as “functions,” even though that terminology isn’t 100% accurate.

Review: Being a consumer of HOFs

I finished the previous lesson by showing a few function definitions like this:

def isEven(i: Int): Boolean = 
    i % 2 == 0

def sum(a: Int, b: Int): Int = 
    a + b

I also showed that isEven works great when you pass it into the List class filter method:

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

scala> val evens = list.filter(isEven)
evens: List[Int] = List(0, 2, 4, 6, 8)

The key points of this are:

  • The filter method accepts a function as an input parameter.
  • The functions you pass into filter must match the type signature that filter expects — in this case creating a function like isEven that takes an Int as an input parameter and returns a Boolean.

Review: Understanding filter’s Scaladoc

You can see the type of functions filter accepts by looking at its Scaladoc:

The Scaladoc text shows that filter takes a predicate, which is just a function that returns a Boolean value.

This part of the Scaladoc:

p: (A) => Boolean
   --------------

means that filter takes a function input parameter which it names p, and p must transform a generic input A to a resulting Boolean value.

TIP: p is just an input parameter to a function. This is just like having a String input parameter, or an Int input parameter, like this:

def x(s: String): String = ???
      ---------

In my example, where list has the type List[Int], you can conceptually replace the generic type A with Int, and read that signature like this:

p: (Int) => Boolean

Because isEven has this type — it transforms an input Int into a resulting Boolean — it can be used with filter.

Review: A lot of functionality with a little code

The filter example shows that with HOFs you can accomplish a lot of work with a little bit of code. If List didn’t have the filter method, you’d have to write a custom method like this to do the same work:

// what you'd have to do if `filter` didn't exist
def getEvens(list: List[Int]): List[Int] = {
    val tmpArray = ArrayBuffer[Int]()
    for (elem <- list) {
        if (elem % 2 == 0) tmpArray += elem
    }
    tmpArray.toList
}

val result = getEvens(list)

Compare all of that imperative code to this equivalent functional code:

val result = list.filter(_ % 2 == 0)

As you can see, this is a great advantage of functional programming. The code is much more concise, and it’s also easier to comprehend.

As FP developers like to say, you don’t tell the computer specifically “how” to do something — you don’t specify the nitty-gritty details. Instead, in your FP code you express a thought like, “I want to create a filtered version of this list with this little algorithm.” When you do that, and you have good FP language to work with, you write your code at a much higher programming level.

“Common control patterns”

In many situations Scala/FP code can be easier to understand than imperative code. That’s because a great benefit of functional programming in Scala is that methods like filter, map, head, tail, etc., are all standard, built-in functions, so once you learn them you don’t have to write custom for loops any more. As an added benefit, you also don’t have to read other developers’ custom for loops.

I feel like I say this a lot, but we humans can only keep so much in our brains at one time. Concise, readable code is simpler for your brain and better for your productivity.

I know, I know, when you first come to Scala, all of these methods on the collections classes don’t feel like a benefit, they feel overwhelming. But once you realize that almost every for loop you’ve ever written falls into neat categories like map, filter, reduce, etc., you also realize what a great benefit these methods are. (And you’ll reduce the amount of custom for loops you write by at least 90%.)

Here’s what Martin Odersky wrote about this in his book, Programming in Scala:

“You can use functions within your code to factor out common control patterns, and you can take advantage of higher-order functions in the Scala library to reuse control patterns that are common across all programmers’ code.”

Given this background and these advantages, let’s see how to write functions that take other functions as input parameters.

Defining functions that take functions as parameters

To define a function that takes another function as an input parameter, all you have to do is define the signature of the function you want to accept.

To demonstrate this, I’ll define a function named sayHello that takes a function as an input parameter. I’ll name the input parameter callback, and also say that callback must have no input parameters and must return nothing. This is the Scala syntax to make this happen:

def sayHello(callback: () => Unit) {
    callback()
}

In this code, callback is an input parameter, and more specifically it is a function input parameter (or FIP). Notice how it’s defined with this syntax:

callback: () => Unit

Here’s how this works:

  • callback is the name I give to the input parameter. In this case callback is a function I want to accept.
  • The callback signature specifies the type of function I want to accept.
  • The () portion of callback’s signature (on the left side of the => symbol) states that it takes no input parameters.
  • The Unit portion of the signature (on the right side of the => symbol) indicates that the callback function should return nothing.
  • When sayHello is called, its function body is executed, and the callback() line inside the body invokes the function that is passed in.

This image reiterates those points:

Now that I’ve defined sayHello, I’ll create a function to match callback’s signature so I can test it. The following function takes no input parameters and returns nothing, so it matches callback’s type signature:

def helloAl(): Unit = { println("Hello, Al") }

Because the signatures match, I can pass helloAl into sayHello, like this:

sayHello(helloAl)

The REPL demonstrates how all of this works:

scala> def sayHello(callback:() => Unit) { 
     |     callback()
     | }
sayHello: (callback: () => Unit)Unit

scala> def helloAl(): Unit = { println("Hello, Al") }
helloAl: ()Unit

scala> sayHello(helloAl)
Hello, Al

If you’ve never done this before, congratulations. You just defined a function named sayHello that takes another function as an input parameter, and then invokes that function when it’s called.

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 callback’s signature. For instance, because this next function takes no input parameters and returns nothing, it also works with sayHello:

def holaLorenzo(): Unit = println("Hola, Lorenzo")

Here it is in the REPL:

scala> sayHello(holaLorenzo)
Hola, Lorenzo

This is a good start. Let’s build on it by defining functions that can take more complicated functions as input parameters.

The general syntax for defining function input parameters

I defined sayHello like this:

def sayHello(callback: () => Unit)

Inside of that, the callback function signature looks like this:

callback: () => Unit

I can explain this syntax by showing a couple of examples. Imagine that we’re defining a new version of callback, and this new version takes a String and returns an Int. That signature would look like this:

callback: (String) => Int

Next, imagine that you want to create a different version of callback, and this one should take two Int parameters and return an Int. Its signature would look like this:

callback: (Int, Int) => Int

As you can infer from these examples, the general syntax for defining function input parameter type signatures is:

variableName: (parameterTypes ...) => returnType

With sayHello, this is how the values line up:

Field/Value sayHello Notes
variableName callback The name you give the FIP
parameterTypes () The FIP takes no input parameters
returnType Unit The FIP returns nothing


Naming your function input parameters

I find that the parameter name callback is good when you first start writing HOFs. Of course you can name it anything you want, and other interesting names at first are aFunction, theFunction, theExpectedFunction, or maybe even fip. But, from now on, I’ll make this name shorter and generally refer to the FIPs in my examples as just f, like this:

sayHello(f: () => Unit)
foo(f:(String) => Int)
bar(f:(Int, Int) => Int)

Looking at some function signatures

Using this as a starting point, let’s look at signatures for some more FIPs so you can see the differences. To get started, here are two signatures that define a FIP that takes a String and returns an Int:

sampleFunction(f: (String) => Int)
sampleFunction(f: String => Int)

The second line shows that when you define a function that takes only one input parameter, you can leave off the parentheses.

Next, here’s the signature for a function that takes two Int parameters and returns an Int:

sampleFunction(f: (Int, Int) => Int)

Can you imagine what sort of function matches that signature?

(A brief pause here so you can think about that.)

Any function that takes two Int input parameters and returns an Int matches that signature, so functions like these all fit:

def sum(a: Int, b: Int): Int = a + b
def product(a: Int, b: Int): Int = a * b
def subtract(a: Int, b: Int): Int = a - b

You can see how sum matches up with the FIP signature in this image:

For me, an important part of this is that no matter how complicated the type signatures get, they always follow the same general syntax I showed earlier:

variableName: (parameterTypes ...) => returnType

For example, all of these FIP signatures follow the same pattern:

f: () => Unit
f: String => Int
f: (String) => Int
f: (Int, Int) => Int
f: (Person) => String
f: (Person) => (String, String)
f: (String, Int, Double) => Seq[String]
f: List[Person] => Person

A note about “type signatures”

I’m being a little loose with my verbiage here, so let me tighten it up for a moment. When I say that this is a “type signature”:

f: String => Int

that isn’t 100% accurate. The type signature is really just this part:

String => Int

Therefore, being 100% accurate, these are the type signatures I just showed:

() => Unit
String => Int
(String) => Int
(Int, Int) => Int
(Person) => String
(Person) => (String, String)
(String, Int, Double) => Seq[String]
List[Person] => Person

This may seem like a picky point, but because FP developers talk about type signatures all the time, I want to take that moment to be more precise.

It’s common in FP to think about types a lot in your code. You might say that you “think in types.”

A function that takes an Int parameter

Recapping for a moment, I showed the sayHello function, whose callback parameter states that it takes no input parameters and returns nothing:

sayHello(callback: () => Unit)

I refer to callback as a FIP, which stands for “function input parameter.”

Now let’s look at a few more FIPs, with each example building on the one before it.

First, here’s a function named runAFunction that defines a FIP whose signature states that it takes an Int and returns nothing:

def runAFunction(f: Int => Unit): Unit = {
    f(42)
}

The body says, “Whatever function you give to me, I’m going to pass the Int value 42 into it.” That’s not terribly useful or functional, but it’s a start.

Next, let’s define a function that matches f’s type signature. The following printAnInt function takes an Int parameter and returns nothing, so it matches:

def printAnInt (i: Int): Unit = { println(i+1) }

Now you can pass printAnInt into runAFunction:

runAFunction(printAnInt)

Because printAnInt is invoked inside runAFunction with the value 42, this prints 43. Here’s what it all looks like in the REPL:

scala> def runAFunction(f: Int => Unit): Unit = {
     |     f(42)
     | }
runAFunction: (f: Int => Unit)Unit

scala> def printAnInt (i: Int): Unit = { println(i+1) }
printAnInt: (i: Int)Unit

scala> runAFunction(printAnInt)
43

Here’s a second function that takes an Int and returns nothing:

def plusTen(i: Int) { println(i+10) }

When you pass plusTen into runAFunction, you’ll see that it also works, printing 52:

runAFunction(plusTen)   // prints 52

The power of the technique

Although these examples don’t do too much yet, you can see the power of HOFs:

You can easily swap in interchangeable algorithms.

As long as the signature of the function you pass in matches the signature that’s expected, your algorithms can do anything you want. This is comparable to swapping out algorithms in the OOP Strategy design pattern.

Let’s keep building on this...

Taking a function parameter along with other parameters

Here’s a function named executeNTimes that has two input parameters: a function, and an Int:

def executeNTimes(f: () => Unit, n: Int) {
    for (i <- 1 to n) f()
}

As the code shows, executeNTimes executes the f function n times. To test this, define a function that matches f’s signature:

def helloWorld(): Unit = { println("Hello, world") }

and then pass this function into executeNTimes along with an Int:

scala> executeNTimes(helloWorld, 3)
Hello, world
Hello, world
Hello, world

As expected, executeNTimes executes the helloWorld function three times. Cool.

More parameters, everywhere

Next, here’s a function named executeAndPrint that takes a function and two Int parameters, and returns nothing. It defines the FIP f as a function that takes two Int values and returns an Int:

def executeAndPrint(f: (Int, Int) => Int, x: Int, y: Int): Unit = {
    val result = f(x, y)
    println(result)
}

executeAndPrint passes the two Int parameters it’s given into the FIP it’s given in this line of code:

val result = f(x, y)

Except for the fact that this function doesn’t have a return value, this example shows a common FP technique:

  • Your function takes a FIP.
  • It takes other parameters that work with that FIP.
  • You apply the FIP (f) to the parameters as needed, and return a value. (Or, in this example of a function with a side effect, you print something.)

To demonstrate executeAndPrint, let’s create some functions that match f’s signature. Here are a couple of functions take two Int parameters and return an Int:

def sum(x: Int, y: Int) = x + y
def multiply(x: Int, y: Int) = x * y

Now you can call executeAndPrint with these functions as the first parameter and whatever Int values you want to supply as the second and third parameters:

executeAndPrint(sum, 3, 11)       // prints 14
executeAndPrint(multiply, 3, 9)   // prints 27

Let’s keep building on this...

Taking multiple functions as input parameters

Now let’s define a function that takes multiple FIPs, and other parameters to feed those FIPs. Let’s define a function like this:

  • It takes one function parameter that expects two Ints, and returns an Int
  • It takes a second function parameter with the same signature
  • It takes two other Int parameters
  • The Ints will be passed to the two FIPs
  • It will return the results from the first two functions as a tuple — a Tuple2, to be specific

Since I learned FP, I like to think in terms of “Function signatures first,” so here’s a function signature that matches those bullet points:

def execTwoFunctions(f1:(Int, Int) => Int, 
                     f2:(Int, Int) => Int, 
                     a: Int, 
                     b: Int): Tuple2[Int, Int] = ???

Given that signature, can you imagine what the function body looks like?

(I’ll pause for a moment to let you think about that.)

Here’s what the complete function looks like:

def execTwoFunctions(f1: (Int, Int) => Int, 
                     f2: (Int, Int) => Int, 
                     a: Int,
                     b: Int): Tuple2[Int, Int] = {
    val result1 = f1(a, b)
    val result2 = f2(a, b)
    (result1, result2)
}

That’s a verbose (clear) solution to the problem. You can shorten that three-line function body to just this, if you prefer:

(f1(a,b), f2(a,b))

Now you can test this new function with the trusty sum and multiply functions:

def sum(x: Int, y: Int) = x + y
def multiply(x: Int, y: Int) = x * y

Using these functions as input parameters, you can test execTwoFunctions:

val results = execTwoFunctions(sum, multiply, 2, 10)

The REPL shows the results:

scala> val results = execTwoFunctions(sum, multiply, 2, 10)
results: (Int, Int) = (12,20)

I hope this gives you a taste for not only how to write HOFs, but the power of using them in your own code.

Okay, that’s enough examples for now. I’ll cover two more topics before finishing this lesson, and then in the next lesson you can see how to write a map function with everything I’ve shown so far.

The FIP syntax is just like the val function syntax

A nice thing about Scala is that once you know how things work, you can see the consistency of the language. For example, the syntax that you use to define FIPs is the same as the “explicit return type” (ERT) syntax that you use to define functions.

I show the ERT syntax in detail in the “Explaining Scala’s val Function Syntax” appendix.

What I mean by this is that earlier I defined this function:

sampleFunction(f: (Int, Int) => Int)

The part of this code that defines the FIP signature is exactly the same as the ERT signature for the sum function that I define in the val Function Syntax appendix:

val sum: (Int, Int) => Int = (a, b) => a + b

You can see what I mean if you line the two functions up:

Once you understand the FIP type signature syntax, it becomes easier to read things like the ERT function syntax and the Scaladoc for HOFs.

The general thought process of designing HOFs

Personally, I’m rarely smart enough to see exactly what I want to do with all of my code beforehand. Usually I think I know what I want to do, and then as I start coding I realize that I really want something else. As a result of this, my usual thought process when it comes to writing HOFs looks like this:

  1. I write some code
  2. I write more code
  3. I realize that I’m starting to duplicate code
  4. Knowing that duplicating code is bad, I start to refactor the code

Actually, I have this same thought process whether I’m writing OOP code or FP code, but the difference is in what I do next.

With OOP, what I might do at this point is to start creating class hierarchies. For instance, if I was working on some sort of tax calculator in the United States, I might create a class hierarchy like this:

trait StateTaxCalculator
class AlabamaStateTaxCalculator extends StateTaxCalculator ...
class AlaskaStateTaxCalculator extends StateTaxCalculator ...
class ArizonaStateTaxCalculator extends StateTaxCalculator ...

Conversely, in FP, my approach is to first define an HOF like this:

def calculateStateTax(f: Double => Double, personsIncome: Double): Double = ...

Then I define a series of functions I can pass into that HOF, like this:

def calculateAlabamaStateTax(income: Double): Double = ...
def calculateAlaskaStateTax(income: Double): Double = ...
def calculateArizonaStateTax(income: Double): Double = ...

As you can see, that’s a pretty different thought process.

Note: I have no idea whether I’d approach these problems exactly as shown. I just want to demonstrate the difference in the general thought process between the two approaches, and in that regard — creating a class hierarchy versus a series of functions with a main HOF — I think this example shows that.

To summarize this, the thought process, “I need to refactor this code to keep it DRY,” is the same in both OOP and FP, but the way you refactor the code is very different.

Summary

A function that takes another function as an input parameter is called a “Higher Order Function,” or HOF. This lesson showed how to write HOFs in Scala, including showing the syntax for function input parameters (FIPs) and how to execute a function that is received as an input parameter.

As the lesson showed, the general syntax for defining a function as an input parameter is:

variableName: (parameterTypes ...) => returnType

Here are some examples of the syntax for FIPs that have different types and numbers of arguments:

def exec(f:() => Unit) = ???  // note: i don't show the function body
                              // for any of these examples

def exec(f: String => Int)     // parentheses not needed
def exec(f: (String) => Int)
def exec(f: (Int) => Int)
def exec(f: (Double) => Double)
def exec(f: (Person) => String)
def exec(f: (Int) => Int, a: Int, b: Int)
def exec(f: (Pizza, Order) => Double)
def exec(f: (Pizza, Order, Customer, Discounts) => Currency)
def exec(f1: (Int) => Int, f2:(Double) => Unit, s: String)

See Also

What’s next

In this lesson I showed how to write HOFs. In the next lesson we’ll put this knowledge to work by writing a complete map function that uses the techniques shown in this lesson.

books by alvin