Functional Programming in PHP

Original author: Lars Strojny
  • 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: $thisnow 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_arrayand $callable(). call_user_funcaccepts a callback function and a list of arguments, after which it calls this callback with the arguments passed. call_user_func_arraydoes 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. callablethis is a meta-type in PHP (that is, consisting of several nested types): it callablecan 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, Closureor 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 functionand lack of implicit returnmake the code look a bit cumbersome. But, nevertheless, it works. Another "building block" in the piggy bank for functional programming.
By the way, array_mapit 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_reducecollects 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 foreachto 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 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 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.

Also popular now: