One of the nice things about programming is that the same problem can be solved using multiple methods. This allows people to show their creativity and come up with incredibly efficient and unique solutions. Example of this can be seen in action when you look for programs that list or find prime numbers.
Similarly, there are different styles of writing a program. Two common types of styles are functional programming and object-oriented programming. The functional style of programming is based on dividing tasks into separate functions. These functions take some arguments and then return a single value. The returned value is based only on the provided input.
This tutorial will teach you how to use functional programming in PHP with lambda functions.
Functional Programming in PHP
Functions are used in programming to group bunch of instructions together that act on optional input to produce some result. There is a special class of functions called first-class functions where a programming language treats its functions as first-class citizens. In simple terms, this means that functions in those programming languages can be passed around as arguments to other functions or used as return values as well as assigned to other variables.
The support for first-class functions was introduced in PHP with version 5.3. This allows us to create anonymous functions (also called lambdas) and closures in PHP. In this tutorial, you will learn all the important concepts that you need to know about lambda functions in PHP.
Anonymous or Lambda Functions in PHP
The use of terms anonymous functions and lambda functions interchangeably can be confusing for people because the PHP manual does not use the term lambda functions anywhere in their entry about anonymous functions. However, dig a bit deeper and you will find an RFC page about closures that discusses lambda functions.
The usefulness of lambda functions can be best understood with the help of some examples. As you already know, there are some built-in functions in PHP that accept other functions as arguments. These callback functions act on provided data and produce a final result. For example, the array_map()
function which accepts a callback function as its first parameter.
Without lambda functions, you will be limited to one of the following three options:
- Define the function somewhere else and then refer to it inside
array_map()
by its name. This will force you to move back and forth in your file to see the function definition whenever needed. - Define the callback functions inside
array_map()
and specify a name. However, you will still need to check that there is no name clash by usingfunction_exists()
. - Just create the function at run time using
create_function()
in PHP. This might seem like a good idea at first but it isn't. It can result is lower readability, performance drop and security issues. In fact, PHP 8.0.0 has removedcreate_function()
from the language.
Lambda or anonymous functions get rid of all these drawbacks by allowing you to define functions in-place without requiring a name. They also compile before runtime so you don't get any performance drop.
Here is an example of anonymous functions in PHP:
<?php $numbers = range(15, 20); print_r(array_map(function($n){ return $n**2; }, $numbers)); /* Array ( [0] => 225 [1] => 256 [2] => 289 [3] => 324 [4] => 361 [5] => 400 ) */ ?>
In the above example, we create a new array with squares of original numbers using array_map()
. As you can see, there was no need to specify a unique name for the callback function and its definition was provided in-place so we didn't have to jump around to see what it does.
Understanding Closures in PHP
Closures are basically lambda functions that can access variables in their parent scope. We have already covered the basics of variable scope in PHP in an earlier tutorial. This increases the usefulness of closures by allowing us to write more flexible and general-purpose functions.
Lets try to rewrite the function in previous section so that it can calculate the exponentiation value to any number instead of just 2. At first, you might try to write it in the following manner:
<?php $numbers = range(15, 20); $pow = 2; print_r(array_map(function($n){ return $n**$pow; }, $numbers)); // PHP Warning: Undefined variable $pow /* Array ( [0] => 1 [1] => 1 [2] => 1 [3] => 1 [4] => 1 [5] => 1 ) */ ?>
This will give you a warning about an undefined variable $pow
and output an array with the wrong values. You can solve this problem by using the global
keyword to access the variable inside the callback function. However, that approach has its own drawbacks which I have already discussed in the tutorial about variable scope.
PHP has a special keyword called use
that gives you access to the variables in parent scope. These lambda functions which take advantage of the use
keyword to access variables that are outside their scope are called closures. The following code example illustrates their use:
<?php $numbers = range(15, 20); $pow = 2; print_r(array_map(function($n) use($pow) { return $n**$pow; }, $numbers)); /* Array ( [0] => 225 [1] => 256 [2] => 289 [3] => 324 [4] => 361 [5] => 400 ) */ ?>
One important thing to remember is that variables are passed inside the function as values. This means that any changes you make to a variable from parent scope will not be visible outside the closure. Here is an example:
<?php $numbers = range(1, 5); $calls = 0; for($i = 6; $i <= 20; $i+=6) { print_r(array_map(function($n) use($i, $calls) { $calls += 1; return $n*$i; }, $numbers)); } echo $calls; /* Array ( [0] => 6 [1] => 12 [2] => 18 [3] => 24 [4] => 30 ) Array ( [0] => 12 [1] => 24 [2] => 36 [3] => 48 [4] => 60 ) Array ( [0] => 18 [1] => 36 [2] => 54 [3] => 72 [4] => 90 ) */ echo $calls; // Outputs: 0 ?>
In the above example, we go through the for
loop 3 times and then increment the value of $calls
by 1 during each of the five calls to our anonymous function. Therefore, the final value of $calls
should have been 15. However, it simply stays at 0.
One way to make the value of $calls
stick across iterations as well as outside the closure is to pass it as a reference.
<?php $numbers = range(1, 5); $calls = 0; for($i = 6; $i <= 20; $i+=6) { print_r(array_map(function($n) use($i, &$calls) { $calls += 1; return $n*$i; }, $numbers)); } /* Array ( [0] => 6 [1] => 12 [2] => 18 [3] => 24 [4] => 30 ) Array ( [0] => 12 [1] => 24 [2] => 36 [3] => 48 [4] => 60 ) Array ( [0] => 18 [1] => 36 [2] => 54 [3] => 72 [4] => 90 ) */ echo $calls; // Output: 15 ?>
Arrow Functions in PHP
Arrow functions were introduced in PHP 7.4 and they simply provide us a way of writing our anonymous or lambda functions more concisely. They take the concept of closures one step further and automatically pass all the variables from the parent scope to the scope of our lambda function. This means that you no longer need to import variables with the use
keyword.
<?php $numbers = range(1, 5); for($i = 6; $i <= 20; $i+=6) { print_r(array_map(fn($n) => $n*$i, $numbers)); } /* Array ( [0] => 6 [1] => 12 [2] => 18 [3] => 24 [4] => 30 ) Array ( [0] => 12 [1] => 24 [2] => 36 [3] => 48 [4] => 60 ) Array ( [0] => 18 [1] => 36 [2] => 54 [3] => 72 [4] => 90 ) */ ?>
Keep in mind that the variables are passed by value instead of reference by default. You can still pass them by reference if you want.
Higher Order Functions
Those functions which either take a function as an argument or return a function as their value are called higher order functions. As we learned in the beginning of this tutorial, PHP has proper support for first-class functions. This means that we can also write our higher order functions in PHP.
Let's use everything we have learned so far to create a higher order function that generates simple sentences from passed values.
<?php function make_sentences($topic) { if($topic == "food") { return fn($a, $b) => "$a eats $b."; } if($topic == "place") { return fn($a, $b) => "$a lives on $b."; } } ?>
The above function accepts a single parameter that specifies the topic for generation of our sentences. Inside the function, we use some conditional statements to return an arrow function which in turn returns a concatenated string.
We will now create three different arrays that contain names of people, fruits and planets. These arrays will be passed two at a time to the array_map()
function along with our function make_sentences()
as callback.
<?php function make_sentences($topic) { if($topic == "food") { return fn($a, $b) => "$a eats $b."; } if($topic == "place") { return fn($a, $b) => "$a lives on $b."; } } $people = ["Adam", "Andrew", "Monty"]; $fruits = ["apples", "bananas", "mangoes"]; $planets = ["Mars", "Earth", "Venus"]; print_r(array_map(make_sentences("food"), $people, $fruits)); /* Array ( [0] => Adam eats apples. [1] => Andrew eats bananas. [2] => Monty eats mangoes. ) */ print_r(array_map(make_sentences("place"), $people, $planets)); /* Array ( [0] => Adam lives on Mars. [1] => Andrew lives on Earth. [2] => Monty lives on Venus. ) */ ?>
As you can see, we were able to generate some simple sentences by taking corresponding elements from two different arrays and joining them in a string. You should try introducing an additional variable inside the make_sentences()
function which will then be used in the arrow functions to create slightly more complicated sentences.
Final Thoughts
We have covered many topics in this tutorial which are related to each other in one way or another. Starting with a brief introduction of functional programming in PHP. We moved on to some useful language features in PHP like lambdas or anonymous functions, closures, and arrow functions. You should now have a good idea of all these terms and how they are different but still related to each other. Finally, we learned how to create our own higher order functions in PHP.
All these concepts will hopefully help you write safer and cleaner code in your future projects.
No comments:
Post a Comment