In a past post we introduced functions and their advantages. In this post, we will extend on the functionality of functions by introducing higher-order functions. Particularly, we’ll see how to include functions among the parameters of a function with an example.
Higher-order functions
A higher-order function is a function that either:
- Accepts a function among its parameters.
- Returns a function.
In other words, a higher-order function is a function that deals with other functions in some way.
Why are they important?
We can see higher-order functions as an extra “tool” in our programming tool-box. This extra tool allows us greater expressive possibilities while using functions.
Let’s see an example.
Accepting a function as a parameter
Let’s say we have a function with two parameters: f(a, b)
. Usually, the parameters a
and b
are variables (numbers, strings, lists, etc). But if any of a
or b
is a function, then f
is a higher-order procedure.
As a concrete example, let’s say we want to filter even values in a list. We could do it with the following function:
def filter(numbers): result = [] for number in numbers: if number % 2 == 0: result.append(number) return result
The parameter numbers
is a list of numbers. For example, if we call filter([1, 2, 3, 4])
, it will return the list of even numbers [2, 4]
.
Now, let’s say we want to filter odd numbers instead. We can do it with minor changes on the test condition to append elements to the result
list:
... if number % 2 == 1: ...
See that the general structure of filter
has not changed. In more general terms, the if
clause establishes the condition that has to be satisfied in order to include a number in the filtered list. This is the motivation we needed to create higher-order functions. Let’s see how to generalize filter
.
Defining higher-order functions
Instead of changing the test performed by the if
clause at each time, we can generalize it by using a test
function:
... if test(number): ...
test
is a function that returns True
when its argument satisfies a certain condition and False
otherwise. To include this function inside filter
‘s body, we just need to include it among its parameters as we did for numbers
:
def filter(numbers, test): result = [] for number in numbers: if test(number): result.append(number) return result
See that the test
parameter is a function called at the if
clause. Congratulations! You have just defined a higher-order function. It wasn’t difficult, right? We just included the function parameter among the other parameters and then used it in the function’s body.
A general recipe for higher order functions of this kind would be:
def function(param_1, ..., func, ...): # Function's body # It's possible to call func(...) here.
Among the paramaters param_1
, … we have a function parameter: func
. By including it among the other parameters, we have now the option of using it inside function’s body with all the advantages that that entails.
Let’s see how to apply it to filter even values. We can test if a number is even with:
def even(n): return n % 2 == 0
If we call even(10)
, it will return True
. If we call even(7)
, it will return False
, and so on.
Finally, to filter even numbers we call:
>>> filter([1, 2, 3, 4, 5], even) [2, 4]
We’re calling filter
with the arguments numbers = [1, 2, 3, 4, 5]
and test = even
.
What’s the big deal?
While this example may seem trivial, we have in fact defined a very powerful function. Here are some examples:
# Helper functions def odd(n): return n % 2 == 1 def greater_5(n): return n > 5 def is_a(char): return char == 'a'
>>> filter(numbers, odd) [1, 3, 5, 7, 9] >>> filter(numbers, greater_5) [6, 7, 8, 9] >>> filter(['a', 'b', 'c'], is_a) ['a']
Using filter
, we’re able to perform any test on a list of numbers. Here we have seen tests using the odd
and greater_5
functions to filter odd values and numbers greater than 5, respectively. Similarly, by just defining a suitable function, we could filter prime numbers, perfect squares, etc.
In fact, we can apply the filter function to any kind of list! See that the last example was passed a list of characters: ['a', 'b', 'c']
. We filtered it with is_a
to get only the 'a'
characters present in the list.
We have seen how to include functions among the parameters of another function, creating higher-order functions. This additional feature allows us a wider range of expressive capabilities using functions. In a future post, we will see how to return functions using functions, a different kind of higher-order function.