PHP has always been a simple, procedural programming language, inspired by C and Perl. In PHP 5, the right object model appeared, but you already know everything about it. But in PHP 5.3 there were closures (closure), which were seriously improved in version 5.4 (hint:
$this
now available by default).
What is it all the same - functional programming?
It has been several years since I started using functional elements in my source code, but I'm still not ready to give a direct and accurate answer to this question. And yet, despite the fact that I do not yet have a clear definition - I can say with confidence when there is an example of functional programming in front of me and when it isn’t. Therefore, I will try to go a little on the other hand: functional programs usually do not resort to changing states, but use pure functions. These pure functions take a value and return a value without changing their input argument. The opposite example is a typical setter in an object-oriented context.
A typical functional programming language also supports high-order functions — these are functions that take as arguments or return other functions. Most of them support such things as currying and
partial function application functions . Also in functional programming languages ​​one can come across an elaborate type system that uses
option type to prevent the appearance of null pointers, which have become commonplace for imperative or object-oriented programming languages.
Functional programming has several seductive properties: the absence of messing with states makes parallelism easier (but not simple — parallelism is never simple), focusing on a function — on the minimum unit of code that could be used again — can lead to interesting things related to their reuse; the requirement for functions to be defined is a great idea for creating stable programs.
')
What can PHP offer?
PHP is not a "real" or "pure" functional language. He is far from that. There is no proper type system, "
cool guys " ride with laughter from our exotic syntax for closures, and there is also an
array_walk()
function, which at first glance looks functional, but allows state changes.
However, there are some interesting “building blocks” for functional programming purposes. To begin with, take
call_user_func
,
call_user_func_array
and
$callable()
.
call_user_func
accepts a callback function and a list of arguments, then calls this callback with the passed arguments.
call_user_func_array
does the same, except that it takes an array of arguments. This is very similar to
fn.call()
and
fn.apply()
in JavaScript (without passing a scope). A much less well-known but excellent function in PHP 5.4 is the ability to call functions.
callable
is a meta type in PHP (that is, consisting of several nested types):
callable
can be a string to call simple functions, an array of
<string,string>
to call static methods, and an array of
<object,string>
to call methods of an object, an instance of
Closure
or anything that does the
__invoke()
magic method, also known as the Functor. It looks something like this:
$print = 'printf'; $print("Hello %s\n", 'World');
PHP 5.4 introduced a new type of “callable”, which allows easy access to the callable meta type.
PHP also supports anonymous functions. As mentioned earlier, the Haskell community laughs heartily at this fact, but the main thing still does not take away - we finally got them. Yes, and the jokes were quite expected, because the syntax of expressions became very heavy. Take a simple example in Python.
map(lambda v: v * 2, [1, 2, 3])
Nice, now take a look at the same code for Ruby:
[1, 2, 3].map{|x| x * 2}
Not bad either, even though we had to use a block and a loosely lambda expression. Ruby also has lambda expressions, but List.maphappens accepts a block, not a function. Let's go to Scala:
List(1, 2, 3).map((x: Int) => x * 2)
As can be seen from the examples, for a strictly typed programming language, the syntax always remains rather compact. Let's rewrite our PHP example:
array_map(function ($x) {return $x * 2;}, [1, 2, 3]);
The
function
keyword and the absence of an implicit
return
make the code look a bit cumbersome. But, nevertheless, it works. Another "building block" in the piggy bank for functional programming.
By the way,
array_map
gives a good start, but it is worth considering that there is also
array_reduce
; here are two more important features.
Functional example from the real world
Let's write a simple program that calculates the total price of a shopping cart:
$cart = [ [ 'name' => 'Item 1', 'quantity' => 10, 'price' => 9.99, ], [ 'name' => 'Item 2', 'quantity' => 3, 'price' => 5.99, ] ]; function calculate_totals(array $cart, $vatPercentage) { $totals = [ 'gross' => 0, 'tax' => 0, 'net' => 0, ]; foreach ($cart as $position) { $sum = $position['price'] * $position['quantity']; $tax = $sum / (100 + $vatPercentage) * $vatPercentage; $totals['gross'] += $sum $totals['tax'] += $tax $totals['net'] += $sum - $tax; } return $totals; } calculate_totals($cart, 19);
Yes, this is a very simple example that will work only for one store. But it uses not too complex calculations, and because of this we can easily redo it, leading to a more functional style.
Let's start by using higher order functions:
$cart = [ [ 'name' => 'Item 1', 'quantity' => 10, 'price' => 9.99, ], [ 'name' => 'Item 2', 'quantity' => 3, 'price' => 5.99, ] ]; function calculate_totals(array $cart, $vatPercentage) { $cartWithAmounts = array_map( function (array $position) use ($vatPercentage) { $sum = $position['price'] * $position['quantity']; $position['gross'] = $sum; $position['tax'] = $sum / (100 + $vatPercentage) * $vatPercentage; $position['net'] = $sum - $position['tax']; return $position; }, $cart ); return array_reduce( $cartWithAmounts, function ($totals, $position) { $totals['gross'] += $position['gross']; $totals['net'] += $position['net']; $totals['tax'] += $position['tax']; return $totals; }, [ 'gross' => 0, 'tax' => 0, 'net' => 0, ] ); } calculate_totals($cart, 19);
Now state changes do not occur, even inside the function itself.
array_map()
returns a new array from the list of positions in the basket with weight, tax, and cost, and the
array_reduce
function gathers together an array of the total. Can we go further? Can we make the program even easier?
And what if we split the program into even smaller parts and see what it actually does:
- Sum the array element multiplied by another element.
- Takes part of percent from this amount.
- Counts the difference between interest and amount.
Now we need a little helper. This little helper will be
functional-php , a small library of functional primitives, which I have been developing for several years now. First of all, there is
Functional\pluck()
, which does the same thing as
_.pluck()
from
underscore.js
. Another useful function from there is
Functional\zip()
. It “compresses” two lists together, optionally using a callback function.
Functional\sum()
summarizes the elements of a list.
use Functional as F; $cart = [ [ 'name' => 'Item 1', 'quantity' => 10, 'price' => 9.99, ], [ 'name' => 'Item 2', 'quantity' => 3, 'price' => 5.99, ] ]; function calculate_totals(array $cart, $vatPercentage) { $gross = F\sum( F\zip( F\pluck($cart, 'price'), F\pluck($cart, 'quantity'), function($price, $quantity) { return $price * $quantity; } ) ); $tax = $gross / (100 + $vatPercentage) * $vatPercentage; return [ 'gross' => $gross, 'tax' => $tax, 'net' => $gross - $tax, ]; } calculate_totals($cart, 19);
An excellent counter-argument immediately arises: is it true that the example has become easier to read? At first glance - definitely not, but from the second and beyond - you get used to it. Personally, it took me some time to get used to the Scala syntax; some time it took to study the PLO and a lot more was spent on understanding functional programming. Is this the most perfect form into which the original example can be turned? Not. But with this code, you saw how much your approach to it changes when you think in terms of applying functions to data structures, rather than using expressions like
foreach
to handle data structures.
What else can you do?
Have you ever encountered
null pointer exceptions ? There is such a thing as
php-option that provides us with the implementation of the polymorphic type “maybe” (maybe) using a PHP object.
This has partial application: it turns a function that takes n parameters into a function that takes <n parameters. How can this be useful? Take the extraction of the first character from the list of strings.
Boring way:
$list = ['foo', 'bar', 'baz']; $firstChars = []; foreach ($list as $str) { $firstChars[] = substr($str, 0, 1); }
Functional path without PFA (partial application of functions):
array_map(function ($str) {return substr($str, 0, 1);}, ['foo', 'bar', 'baz']);
Path with PFA and using
reactphp/curry
(my favorite implementation of PHP carring):
use React\Curry; array_map(Curry\bind('substr', Curry\…(), 0, 1), ['foo', 'bar', 'baz']);
Yes. ...
(HORIZONTAL ELLIPSIS, U+2026)
is the correct function name in PHP. But if for some reason you don’t really like it, you can use
useCurry\placeholder()
instead.
That's all
Functional programming is a very exciting topic; if I were asked what became the most important thing I had learned in recent years, then I would immediately say - familiarity with functional paradigms. It is so unlike anything you have tried before that your brain will hurt. Well, in a good way.
And finally: read “
Real World Functional Programming ”. It is full of good tips and examples of use in practice.
Waiting for your comments and amendments to the article in personal messages.