
Functional Programming in PHP
- Transfer
PHP has always been a simple, procedural programming language, drawing its inspiration from C and Perl. The correct object model appeared in PHP 5, but you already know everything about it. But in PHP 5.3, closures appeared, which were seriously improved in version 5.4 (hint:
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 confidently say when I have before me an example of functional programming, and when not. Therefore, I will try to go in a little different way: 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 supports such things as karring ( currying ) and partial function application ( partial function application ). Also in functional programming languages you can find a carefully thought-out type system that uses the option type to prevent the appearance of null pointers, which have become commonplace for imperative or object-oriented programming languages.
Functional programming has several tempting properties: the lack of fuss with states makes parallelism easier (but not simple - parallelism is never simple), focusing on functions - on the smallest unit of code that could be used again - can lead to interesting things related to their reuse; requiring functions to be defined is a great idea for creating stable programs.
PHP is not a "real" or "pure" functional language. He is far from it. There is no proper type system, “ tough guys ” ridicule from our exotic syntax for closures, and yet there is a function
However, there are some interesting “building blocks” for functional programming purposes. To begin with, take
PHP 5.4 introduces a new type of “callable” that 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 is not to take it away - we finally got them. And the jokes were quite expected, because the syntax of the expressions became very difficult. Take a simple Python example.
Nice, now take a look at the same code for Ruby:
Also not bad, although we had to use a block and a lax lambda expression. Ruby also has lambda expressions, but List.maphappens accepts a block, not a function. Let's move on to Scala:
As you can see from the examples, for a strongly typed programming language, the syntax always remains quite compact. We rewrite our example in PHP:
Keyword
By the way,
Let's write a simple program that calculates the total price of a shopping basket:
Yes, this is a very simple example that will work for only one store. But it uses not too complicated calculations, and thanks to this we can easily remake it, leading to a more functional style.
Let's start by using higher order functions:
Now state changes do not occur, even inside the function itself.
But what if we break the program down into even smaller parts and see what it actually does:
Now we need a little helper. This little helper will be functional-php , a small library of functional primitives that I have been developing for several years. For starters, there is
An excellent counterargument arises immediately: is it true that the example has become easier to read? At first glance - definitely not, but from the second onwards - you get used to it. Personally, it took me a while to get used to the Scala syntax; the study of OOP took some time and a lot more went into understanding functional programming. Is this the most perfect form you can turn an original example into? 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
Have you ever encountered null pointer exceptions ? There is such a thing as php-option , which provides us with an implementation of the polymorphic type “maybe” using a PHP object.
There is a partial application for this: it turns a function that takes n parameters into a function that takes
Boring way:
Functional path without PFA (partial application of functions):
Path with PFA and using
Yes. ...
Functional programming is a very exciting topic; if I were asked what became the most important thing I learned in recent years, then I would immediately answer - 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 " Functional programming in the real world » ( Real World Functional Programming ). It is full of good tips and practical examples.
I look forward to your comments and corrections to the article in private messages.
$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 confidently say when I have before me an example of functional programming, and when not. Therefore, I will try to go in a little different way: 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 supports such things as karring ( currying ) and partial function application ( partial function application ). Also in functional programming languages you can find a carefully thought-out type system that uses the option type to prevent the appearance of null pointers, which have become commonplace for imperative or object-oriented programming languages.
Functional programming has several tempting properties: the lack of fuss with states makes parallelism easier (but not simple - parallelism is never simple), focusing on functions - on the smallest unit of code that could be used again - can lead to interesting things related to their reuse; requiring 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 it. There is no proper type system, “ tough guys ” ridicule from our exotic syntax for closures, and yet there is a function
array_walk()
that at first glance looks functional, but allows changing states. 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, after which it calls this callback with the arguments passed. call_user_func_array
does the same, except that it takes an array of arguments. It is very similar to fn.call()
andfn.apply()
in JavaScript (without passing scope). A much less well-known but great feature in PHP 5.4 is the ability to call functions. callable
this is a meta-type in PHP (that is, consisting of several nested types): it callable
can be a string for calling simple functions, an array of
for calling static methods, and an array of
for calling methods of an object, instance, Closure
or anything else that performs a magic method __invoke()
, also known as Functor. It looks something like this:$print = 'printf';
$print("Hello %s\n", 'World');
PHP 5.4 introduces a new type of “callable” that 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 is not to take it away - we finally got them. And the jokes were quite expected, because the syntax of the expressions became very difficult. Take a simple Python example.
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}
Also not bad, although we had to use a block and a lax lambda expression. Ruby also has lambda expressions, but List.maphappens accepts a block, not a function. Let's move on to Scala:
List(1, 2, 3).map((x: Int) => x * 2)
As you can see from the examples, for a strongly typed programming language, the syntax always remains quite compact. We rewrite our example in PHP:
array_map(function ($x) {return $x * 2;}, [1, 2, 3]);
Keyword
function
and lack of 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
it gives a good start, but it is worth considering that there is also array_reduce
; here are two more important features.Real world functional example
Let's write a simple program that calculates the total price of a shopping basket:
$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 for only one store. But it uses not too complicated calculations, and thanks to this we can easily remake 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 value, and the function array_reduce
collects an array of the total amount together. Can we go further? Can we make the program even easier? But what if we break the program down into even smaller parts and see what it actually does:
- Summarizes an element of an array multiplied by another element
- It takes part of the percentage of this amount
- Counts the difference between percent and amount
Now we need a little helper. This little helper will be functional-php , a small library of functional primitives that I have been developing for several years. For starters, there is
Functional\pluck()
one that does the same thing as _.pluck()
from underscore.js
. Another useful feature from there is this Functional\zip()
. It “compresses” two lists together, optionally using a callback function. Functional\sum()
summarizes list items.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 counterargument arises immediately: is it true that the example has become easier to read? At first glance - definitely not, but from the second onwards - you get used to it. Personally, it took me a while to get used to the Scala syntax; the study of OOP took some time and a lot more went into understanding functional programming. Is this the most perfect form you can turn an original example into? 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 process data structures.What else can be done?
Have you ever encountered null pointer exceptions ? There is such a thing as php-option , which provides us with an implementation of the polymorphic type “maybe” using a PHP object.
There is a partial application for this: it turns a function that takes n parameters into a function that takes
$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 currying for PHP):use React\Curry;
array_map(Curry\bind('substr', Curry\…(), 0, 1), ['foo', 'bar', 'baz']);
Yes. ...
(HORIZONTAL ELLIPSIS, U+2026)
this is the correct function name in PHP. But if for some reason you didn’t like it very much, you can use it instead useCurry\placeholder()
.That's all
Functional programming is a very exciting topic; if I were asked what became the most important thing I learned in recent years, then I would immediately answer - 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 " Functional programming in the real world » ( Real World Functional Programming ). It is full of good tips and practical examples.
I look forward to your comments and corrections to the article in private messages.