Scala Higher-Order Functions

Ilker Konar
Mercury Business Services
5 min readMar 24, 2024

--

In the Scala programming language, higher-order functions, in short, are functions that either take a function as a parameter or return a function.

In Scala, functions are first-class values. It means that functions are treated like any other value or data type. Functions can be assigned to variables, passed as arguments to other functions and returned as results from other functions. This flexibility allows functions to be used in a wide range of contexts and makes functional programming constructs like higher-order functions, function literals (anonymous functions), and function composition possible in Scala.

Functions as parameter

Suppose we have a list of integers, and we would like to create a new list by adding 3 to each element in the original list. We can achieve this by using the addThreeToEach function in the following code.

// Define a higher-order function that takes a function as an argument
private def addThreeToEach(numbers: List[Int], f: Int => Int): List[Int] = {
numbers.map(f)
}

// Define a function that adds 3 to a number
private def addThree(n: Int): Int = {
n + 3
}

@main def run(): Unit =
val numbers = List(1, 2, 3, 4, 5)

// Use the higher-order function to add 3 to each number in the list
val result = addThreeToEach(numbers, addThree)
println(result) // Output: List(4, 5, 6, 7, 8)

The higher-order function addThreeToEach is a function that takes a list of integers and a function parameter, and returns each element incremented by 3.

Without defining the addThree method, we can also pass the function body directly to the addThreeToEach function when calling it:

// Define a higher-order function that takes a function as an argument
private def addThreeToEach(numbers: List[Int], f: Int => Int): List[Int] = {
numbers.map(f)
}

@main def run(): Unit =
val numbers = List(1, 2, 3, 4, 5)

// Use the higher-order function to add 3 to each number in the list
val result = addThreeToEach(numbers, n => n + 3)
println(result) // Output: List(4, 5, 6, 7, 8)

Furthermore, we don’t need to define a higher-order function explicitly in the code; we can define it anonymously while calling it:

@main def run(): Unit =
val numbers = List(1, 2, 3, 4, 5)

// The higher-order function definition when printing the result
println(((list:List[Int]) => list.map(n => n + 3))(numbers))
// Output: List(4, 5, 6, 7, 8)

Functions that return functions

In particular scenarios, it can be necessary to generate a function. Here’s a demonstration of a method that yields a function:

// Define a function that returns another function
private def bonusCalculator(performancePoint: Int): (Double) => Double = (salary:Double) => {
performancePoint match
case 1 => 0
case 2 => 0.3 * salary * 0.2
case 3 => 0.3 * salary * 0.5
case 4 => 0.3 * salary * 0.8
case 5 => 0.3 * salary
}

@main def run(): Unit =
// Calculate the bonus for a staff whose salary is 1000
// and performance point is 4
println( bonusCalculator(4)(1000) )

Notice the return type of bonusCalculator: (Double) => Double. This means that the anonymous function returned by bonusCalculator takes a Double as input and returns a Double as output. The bonusCalculator function returns the calculator function based on the performancePoint parameter. In the function body, performancePoint is checked using pattern matching, and the corresponding function definition is returned from the bonusCalculator function.

In the println statement, bonusCalculator(4) returns the associated (Double) => Double function based on the parameter 4 (performancePoint). Then, with the (1000) code, the parameter of the returned function is applied, and the returned function is invoked in this way.

Function Composition

Function composition is a technique where we can chain together multiple functions, applying the result of one function as the parameter of another. It allows us to create a new function that combines the behavior of the individual functions in a chain.

Below, you can see an example:

// Define a higher-order function that takes a function f and returns a new function
private def compose[A, B, C](f: B => C, g: A => B): A => C = {
(x: A) => f(g(x))
}

// Define two basic functions
private def increment(x: Int): Int = x + 1
private def square(x: Int): Int = x * x

// Use the higher-order function to compose the two functions
private val composedFunction: Int => Int = compose(square, increment)

@main def run(): Unit =
// Test the composed function
val result: Int = composedFunction(4)
println(result) // Output: 16 (square(increment(4)) = square(5) = 25)

We define 2 simple functions. The increment function adds 1 to its input, while the square function squares its input.

At first glance, the compose function may seem difficult and complicated to understand, but when you examine it step by step, you will realize that it is not as challenging as it initially appears. It combines increment and square into a new function that first increments its input and then squares the result.

The (f: B => C, g: A => B) indicates the two functions used as parameters in the compose method. In the g function, you input A and it returns B. In the f function, you input B, which is the output of g, and it returns C.

The A => C indicates that the compose function returns a function definition. When you input A (the parameter of the g function) into this returned function, it yields C, which is the output of the f function.

In the function body, f(g(x)) implies the return of a function that first calls the g function and then calls the f function with the result of the g function.

The composedFunction combines increment and square into a new function that first increments its input and then squares the result.

Conclusion

In this tutorial, we implemented some examples of higher-order functions in Scala, showcasing their power and versatility. As demonstrated, higher-order functions enable us to produce readable, concise code while promoting code reuse and modularity. By leveraging higher-order functions effectively, developers can enhance their Scala programming skills and create more maintainable and flexible codebases. However, it’s important to be mindful of potential challenges and pitfalls when working with higher-order functions. We encourage further exploration of this topic through additional resources and advanced topics to deepen your understanding and proficiency in Scala programming.

--

--