But what is a higher-order function and why would we want to use one? What advantages does it give us and can we use it to simplify our code? In this article, we’ll talk about higher-order functions in PHP specifically, but will show how they’ve been used in other languages for comparison.
Before we can get into what a higher-order function is, we must first understand that we have to have a language that can support a feature called first-class functions (aka first-order functions). This means that the language treats functions as first-class citizens. In other words, the language treats functions like it does variables. You can store a function in a variable, pass it to other functions as a variable, return them from functions like variables and even store them in data structures like arrays or object properties like you can with variables. Most modern languages these days have this feature by default. All you really need to know is that a function can be passed around and used much like variables are.
For our purposes, we’ll be focusing mostly on passing functions as arguments and returning functions as results, and briefly touching on the concept of non-local variables and closures. (You can read more about these concepts in sections 1.1, 1.4 and 1.3 of the Wikipedia article that was linked to in the previous paragraph.)
What Are Higher-order Functions?
There are two main characteristics that identify a higher-order function. A higher-order function can implement just one or both of the following ideas: a function that takes one or more functions as an input or returns a function as an output. In PHP there’s a keyword that’s a clear giveaway that a function is higher-order: the keyword
callable. While this keyword doesn’t have to be present, the keyword makes it easy to identify them. If you see a function or method that has a callable parameter, it means that it takes a function as input. Another easy sign is if you see a function return a function using its
return statement. The return statement might be just the name of the function, or could even be an anonymous/in-line function. Below are some examples of each type.
function echoHelloWorld() echo "Hello World!"; function higherOrderFunction(callable $func) $func(); higherOrderFunction('echoHelloWorld');
Here are some simple examples of higher-order functions that return a function:
function trimMessage1() return 'trim'; function trimMessage2() return function($text) return trim($text); ; $trim1 = trimMessage1(); echo $trim1(' hello world '); $trim2 = trimMessage1(); echo $trim2(' hello world ');
As you can imagine, you can take in a function and also use that to generate another function that’s returned. Pretty neat trick, right? But why would you want to do any of this? It sounds like something that would simply make things more complicated.
Why Would You Use or Create a Higher-order Function?
There are several reasons you might want to create higher-order functions in your code. Everything from code flexibility, code reuse, code extension or to imitate a code solution you saw in another program. While the reasons are numerous, we’ll cover a few of them here.
Adding code flexibility
Higher-order functions add a ton of flexibility. Based on the previous examples, you probably can see a few uses for something like this. You can write a single function that takes in a whole suite of different functions and uses them without having to write code to execute them individually.
Maybe the higher-order function itself doesn’t know what type of function it will receive. Who said that the function has to know anything about the function it’s taking in? One minute the input function could be an
add() function, and in the next it could be a
divide() function. In either case it just works.
Easily expand your code
This functionality also makes it easier to add additional input functions later. Let’s say you have
divide(), but down the road you need to add a
sum() function. You can write the
sum() function and pipe it through the higher-order function without ever having to change it. In a later example, we’ll demonstrate how we can use this functionality to create our own
Imitate features of another language
Another reason you might want to use a higher-order function in PHP is to simulate the behavior of something like a decorator in Python. You “wrap” a function inside another function to modify how that function behaves. For instance, you could write a function that times other functions. You write a higher-order function that takes in a function, starts a timer, calls the function, then ends the timer to see how much time has elapsed.
Examples of Existing Higher-order Functions in PHP
It’s really simple to find examples of higher-order functions in PHP. Look at the PHP documentation and find a function/method that takes a
callable input parameter and you’ve found one. Below are a few examples of commonly used higher-order functions. You might have even used them without ever knowing they were higher-order functions.
The higher-order dynamic duo:
$arrayOfNums = [1,2,3,4,5]; $doubledNums = array_map(function($num) return $num * 2; , $arrayOfNums); var_dump($doubledNums);
Let’s see how to use another one, this time with a filter function we’ve defined separately:
$arrayOfNums = [1,2,3,4,5]; function isEven($num) return ($num % 2) === 0; $evenNums = array_filter($arrayOfNums, 'isEven'); var_dump($evenNums);
array_map are two very popular higher-order functions that you’ll find in plenty of code projects. Another one you might use is
call_user_func, which takes in a function and a list of arguments and calls on that function. This is essentially a customized higher-order function. Its whole purpose is to call other functions that the user has defined:
function printCustomMessage($message) echo "$message"; call_user_func('printCustomMessage', 'Called custom message through the use of call_user_func!');
How to Create Your Own Higher-order Functions
We’ve shown lots of examples of how higher-order functions work in PHP, and we’ve already created a few custom ones to demonstrate their various purposes. But let’s show off the flexibility a bit more with a new function we’ll call
calc(). This function will take in two arguments and a function that will operate on those two arguments to give us a result:
function add($a, $b) return $a + $b; function subtract($a, $b) return $a - $b; function multiply($a, $b) return $a * $b; function calc($n1, $n2, $math_func) return $math_func($n1, $n2); $addedNums = calc(1, 5, 'add'); $subtractedNums = calc(1, 5, 'subtract'); $multipliedNums = calc(1, 5, 'multiply'); echo "Added numbers: $addedNumsn"; echo "Subtracted numbers: $subtractedNumsn"; echo "Multiplied numbers: $multipliedNumsn";
Notice that our
calc() function was written very generically to take in a set of other functions and calls them with parameters
$n2. In this case, our calculation function doesn’t care what the function it takes in does. It just passes along the parameters to the function and returns the result.
But wait a minute: our boss just asked us to add a function to add two strings together in our existing system. But concatenating strings isn’t your typical math operation. However, with our setup here, we just need to add in the string concatenation and pipe it through
function addTwoStrings($a, $b) return $a . " " . $b; $concatenatedStrings = calc('Hello', 'World!', 'addTwoStrings');
While this example is very contrived, it demonstrates how you might use the concept in bigger projects. Maybe the higher-order function determines how events are handled. Maybe, based on the event triggered, you can route it through a handler function you pass in. The possibilities are endless.
def say_message(func): def wrapper(): print('Print before function is called') func() print('Print after function is called') return wrapper def say_hello_world(): print("Hello World!") say_hello_world = say_message(say_hello_world) say_hello_world()
function say_message(func) return function() console.log("Print before function is called"); func(); console.log("Print after function is called"); function say_hello_world() console.log("Hello World!"); say_hello = say_message(say_hello_world); say_hello();
Now lastly, let’s compare this with PHP:
function say_message($func) return function() use ($func) echo "Print before function is called"; $func(); echo "Print after function is called"; ; function say_hello_world() echo "Hello World!"; $say_hello = say_message('say_hello_world'); $say_hello();
A Quick Word on Lambdas/Anonymous Functions
Many times, when you’re working with higher-order functions, you may see the use of inline anonymous functions (aka lambdas). In some of the examples we’ve seen above, I’ve used this format. In addition to that, I’ve also used the more explicit version of defining the function first and then passing it to our higher-order function. This is so that you get used to seeing both versions. More often than not, you’ll see the inline lambda version in frameworks that make heavy use of higher-order functions. Don’t let this format throw you. They’re just creating a simple function, without a name, and using that in place of where you’d have given the stand-alone function name.
In this article, we covered the basic definition of what makes a function a higher-order function. Any function that takes in a function or returns a function (or both) can be considered a higher-order function. We also talked a bit about why you might want to create these types of functions and what their advantages are.
We then showed some examples of existing higher-order functions in PHP, like
array_filter, and then went on to create our own higher-order function called
I hope you learned a bit more about higher-order functions and why you might want to consider them in your next large-scale solution. They can offer many advantages and reduce boiler-plate code that may be redundant.
If you’d like to learn more about functional programming in PHP, you can consult our tutorial.
Thanks for reading!