Functional Programming in PHP — An Introduction

Viktor Daróczi
14 min readApr 1, 2021

Why Functional Programming?

The web is getting filled with articles praising Functional Programming (FP), sometimes even starting with whipping Object Oriented Programming (OOP). Although the paradigm started to gain some momentum lately, there’s nothing new about it, considering it’s been around for 60+ years. You read that number right, FP may be older than your ma. So why is all the hype happening all of a sudden? Have the people of internet just reinvented the wheel? Does it take them so much time to learn a programming paradigm? But the OOP term itself is coined well after FP.

It’s not that anybody prevented us using FP in the meantime. Lisp was there all along and it’s doing fine. However recently whole new branches of the industry has emerged and have characteristics that could benefit from the FP approach. We have data available in bigger masses than ever. We have hardware featuring multiple CPU cores as available as within the reach of our hands. Artificial Intelligence gets used heavily in some ever-growing areas. These are the fields that FP was targeting since the beginning.

That’s not all though. With the growth of the software industry, we’ve also learned that writing bug-free, stable software is not that easy. Facing the challenge more and more often, we started to appreciate any means that can help us in our struggle. Here comes FP, that has a proven background originating from mathematics itself. It behaves according to laws just like addition and multiplication. No programmer questions the validity and reliability of such operations. FP promises the same reliability in the domain of programming. How? Because it defines itself based on principles deriving right from the venerable theories of Lambda Calculus and Category Theory. In other words that means reasoning about our programs gets way easier, because all the relations, operations and patterns were defined and proven long before by mathematicians. At least if we do follow those principles that is.

Why PHP?

Besides that ‘why not?’, FP is everywhere. Why would PHP be an exception? Can you do FP in C++? Definitely! How about C? Well, for an extent, sure. Nevertheless, you can embed Lisp anytime into your C code to do the FP heavy lifting (it has regular updates too). Watching release notes of languages, that are well known of their OOP paradigm, like Java and C# is like reading reports about how these languages themselves are trying to catch up on the FP front. There’s a huge demand to do so as more and more people gain understanding of FP.

Some of our favorite tools are already implemented using FP, and with asynchronous, non-blocking and distributed systems more to come every day. Ever wondered why jQuery, Node.js, Swoole and ReactPHP (among others) have sometimes a somewhat irregular API at least comparing to what we got used to in OOP? That’s why.

So if we’re already often using FP without ever being actually aware of it, why not learning this paradigm properly so that we can use it more efficiently?

Although using FP tools is a proof in itself that FP can be mixed with OOP without a problem, in the present and the followup articles we’ll trying to focus on how we could use FP on its own right. So this is not your average map-reduce article, but more like ‘how can I actually use a monad’ kind of series of articles.

If terms like ‘functor’, ‘applicative’ and ‘higher-kinded types’ scare you away, I can offer you a refuge where I will explain you what are these things for, how to use them in your familiar and safe environment what PHP may mean to you, without going further down into the rabbit hole of mathematical theories. I suspect that you would educate yourself in math rather than programming if those things were your main interest.

Does that sound about right? Then let’s get started with it.

Functional experiments

Let’s execute a series of experiments in which we discover the main features of FP together. How is it better than other paradigms we’re used to do? How is it different? And how is it the same?

I highly recommend to get your hands dirty and actually code these things along with me. This is a completely different paradigm, meaning that hardly any of your knowledge remains valid in this new domain, and you can read books about that only to get confused more, and facing a bitter disappointment when you end up trying to actually use what you thought you’ve already learned. To avoid this, you’d better try to implement everything yourself all along. Believe me, they do look super simple at first, but they can turn into a particularly cruel maze especially designed to get you lost shortly after. The only protection against this is to try every code samples out and gain first-hand experience.

Have you seen some guys who started to do ‘OOP’ but kept on writing kilometers long methods, only because that’s what they were comfortable with? Without proper understanding, FP would not offer any more benefits either.

If you’re still following, you’re good to go.

Prerequisites

That we were able to pick PHP as a scene to our investigation is due to the fact that it supports the most important FP concepts right out of the box. Furthermore, it even provides some implementations to the most common patterns like map and reduce. Some FP is already possible to do in PHP without the need of implementing anything new. But we’re not going to do any FP, we strive for purity, and targeting FP at its finest.

For that we need to know the basic concepts. Which we will skim through right now.

Note that all the examples are written in PHP 8. Although some of them may work in earlier versions as well (or can be converted easily), for convenience reasons I recommend to go with the latest features provided by the language.

Functions hand picked

FP is hardly can be called like that without functions. But this naming is somewhat misleading, because PHP mixes an unrelated concept also in, and using the same name for both. You may heard of that besides functions, other languages like Fortran and Ada (Pascal, Delphi) provide another mean to structure your code, called subroutines or procedures. These code organization units differ from functions in one important aspect: they don’t return a value.

In PHP on the other hand, it’s not mandatory for a function to return a value. However this turns out to be crucial from FP aspect, and we will deal with only the kind of functions that do return a value.

It also doesn’t help that when implementing OOP, PHP inherits the same approach and call everything a method regardless of whether they return a value or not (although it’s valid in OOP). So again, FP is a little picky with functions and does not consider subprograms that don’t return a value ‘functions’.

Functions from the High Castle

From FP perspective, just as well as there are ‘functions’ that are under-qualified to be even called a function, also exist functions that are in high regard, the nobility of functions — so to say — thanks to their distinguished role. Such functions, AKA ‘higher-order functions’ (just kidding, they are actually called higher-order functions), deserved such an honor because they themselves are able to handle functions. Hence any function that expects another function as an input, or returns another function is considered a higher-order function. This is very important, since this very characteristic makes it possible to compose functions and create patterns.

Again we’re lucky with PHP from this perspective. While having a somewhat clunky syntax, it still lets us passing functions around in the form of callables or Closures. Furthermore, it even lets us pass methods, functions, anonymous functions and arrow functions. But what’s the difference? Remember we only deal with functions that actually return a value. So with all these kinds of functions, we’ll only do that: returning a value.

Callables: anything that can be called according to PHP’s standards. That can even be a string calculated runtime, returned from another function as long as it refers to a valid function (by name) that can be executed by PHP. I won’t repeat the definition of callables here, but instead consider the following snippet:

Here, I intentionally declared makeCallable to return a string and not a callable while call_user_func expects a callable according to the documentation, but since PHP is not a strictly typed language, this string is actually considered a callable as long as it refers to a real function that can be called. In other languages I could use the name directly, and by intuition we might expect the same from PHP, but it turns out such kindness is reserved for constants only, and won’t work. So keep on passing strings referring to actual pieces of code, or read further and use something else entirely.

Closures: are actually instances of the Closure class in PHP, that implement some methods that can be used to call our function and bind some context to it. Beware though that PHP does not always capture the context automatically, but expects us to bind it explicitly either by bind or by use (see the relevant parts of the docs for details).

Now this class has a private constructor, so nobody is expected to instantiate it like that, but anonymous functions and arrow functions are actually stored as Closure instances internally, and therefore don’t require any conversion, while Closure also provides a helper method to convert callables into Closures, as follows: Closure::fromCallable($someCallable) — returns an instance of Closure. If you feel safer to pass around Closures, you can do it with this method, but we have other options as well.

Anonymous functions: we’re getting somewhere. It turns out functions don’t need to have a name to operate. We can define them on the fly even when passing them as argument:

We’ve just made some FP passing of functions around the PHP way. But wait, there’s more!

Arrow functions: if you’re sick of typing function , return , semicolon and curly braces all the time, PHP also offers a shorthand syntax provided you don’t want to write several instructions in the body:

fn() => "I'm pretty much of an arrow function"

As opposed to the anonymous functions, arrow functions do capture the context automatically, so one less thing to worry about.

Finally, I could rewrite line 15 in the above example as follows:

echo wrap(fn($a) => "'$a'", 'c');

The good thing about these is that they all are more or less compatible and serve well our FP purposes.

Refuse, resist!

We all know that sometimes certain kind of resistance is justified. This also applies to our data if we don’t want to get surprises like

Some one’s been drinking from my cup!

Who tho? This boils down to the principle of immutability. Meaning that our values refuse to change and resist attempts to alter them.

Books have been written on how to cope with shared data between threads and deal with communication between processes, yet it never ceased to be a challenge. Nowadays we also have distributed systems and microservices, and all welcome means targeting to stay safe from unwanted changes of data. Of course, one can feel safer if the containers for data are designed to be immutable.

We also learned that it’s a good practice to define and use constants whenever possible. PHP has its own interpretation on that leading to a somewhat different approach than what we can find in other languages like JavaScript:

const c = Math.random();

First of all, PHP does not allow us to define a value of a constant dynamically, but rather expects us to provide scalar values. This is not very useful.

Secondly, we cannot even define constants wherever we feel like, but their definition is limited to top and class level.

To be fair, these two concerns are only valid when we try to use the const keyword, while we are good to go with the more clumsy define syntax:

You might have noticed that I’ve been a bit ‘unconventional’ here. That’s another annoying feature of constants in PHP, that we’re expected to capitalize constants by convention as if they were something exceptional to shout out. Compare this with a similar line from Scala:

val testName = "test1.txt"

or Rust:

let testName = "test1.txt";
let mut proneToChange = 1; // Beware the mutable state!

Man, they are immutable by default! There’s nothing to be scared of.

It turns out that besides define and const there’s another way to define immutable values, and it’s baked into PHP right from the beginning. Consider this:

function testFileName(): string {
return "test1.txt";
}

Can I redefine testFileName to point to another value? Not as long as it’s in the scope. Just like a proper immutable value (I would not utter something like ‘immutable variable’ since its value never varies). So it’s safe to be used from mutability perspective. However this kind of definition has quite an overhead, uses more memory and superfluously defines a function where a simple value would do making the implementation also unnecessarily relatively slower.

But frankly, we hardly ever need this only to create an immutable value, since most of our values come from somewhere, we don’t just define them ad-hoc in the code. They may come from configuration files, returned from queries, requests, generators, inputs and calculations with those. We will see later that in FP we use the declarative approach as opposed to the imperative approach, and we won’t need hardly any of the variables that are a must in the imperative style, like temporary variables, so that our code can go almost entirely $ free. Well, that’s a considerable improvement in the style of the language!

There’s only one question left. How can we alter state if we don’t change values. The thing is, we don’t. FP makes us possible to cleverly wire inputs and outputs together and stream data flow in a way that everything in between remains pure.

For risks and side effects…

Other source of surprises come from when we delegate a task to a certain piece of code and it happens to do other shady businesses as well on its own. This goes against several principles like single responsibility and separation of concerns among others. When we rely on something we expect it to do one thing and do that right. This is such an important issue, that programmers can’t stop emphasizing it regardless of the paradigm they follow (see SOLID principles).

We want clear APIs, that solely do one thing they are responsible for, and don’t surprise us with side effects, changes that we didn’t mean, or didn’t expect to occur along with the task we delegated.

We tend to picture this as a vending machine. We throw the coin in, and it spits the snacks out. Indeed we’d be very disappointed, if it refused to give us what we’ve paid for. But not only that, we also don’t expect it to do anything else that is not strictly related to releasing our goods. Like what if it also invalidates our tickets to the train? Cancels our hotel reservation. Or shakes our soda like crazy so that it explodes when we open it. That would be terrible!

We don’t like side effects, and we don’t like to see them neither mentioned on medicine inlays nor experience them running programs unsuspectingly.

Similarly, we dislike libraries, APIs, classes, methods, functions that have side effects. And that brings us to our next section.

Code as clean as it gets

I’ve mentioned ‘pure’ a couple of times for now, but I didn’t really mention what does it mean. If a paradigm only has functions as building blocks (we saw that even values can be defined as functions easily), then we can imagine that at least it distinguishes functions from functions. Indeed, we went through some of these starting with the problem that not all are functions that are called ‘functions’, and that not all functions are created equal.

There’s one more: some functions are cleaner than others.

The easy part is that we already know what is the criteria for a function to be considered clean. A function is pure when it’s free from side effects. So basically it’s like an ideal vending machine that always produces the same kind of soda can in response to the same amount of coins and pressing the same button (and never gets out of supplies).

That means our function will never behave unexpectedly because it replies with the same output on the same input, and there’s nothing else either that could change its behavior because it depends solely on its input (we could consider refilling the supplies also an input so that we can survive with the vending machine metaphor).

That’s the reason we need immutable values, because otherwise we cannot guarantee that our function will always return the same output in response to the same input. Consider this:

Our function depended on something else than its inputs. Now one could argue that variable $a was just an input to the function, but even if it’s so, it’s not mentioned in the function’s signature, and one can only tell that when browsing its source, digging into implementation details, while it can be hidden deep in the code, and our IDE is helpless raising our awareness to the risks of calling such a function.

This is not transparent, it’s just the opposite, as if it was trying to hide its dependency on the global variable. It’s not difficult to come up with ideas to improve this function, but there’s another thing that lurks in the dark waiting for causing some trouble: class instances.

Here the Singleton pattern makes sure we’ll only have one instance of our class ever. Yet that does not prevent our instance from changing. This is like creating a constant in JavaScript, and assuming it can never change, while it’s still willing to change the values of its properties:

>> const o = { a: 'a' };
<- undefined
>> o.a = 'b';
<- "b"
>> o.a
<- "b"

Const has no power here. How can one feel safe while Troyans are waiting for betraying them? When we say ‘immutable’, we mean it!

If we could just get rid of these threats, our life would be easier. This is exactly the goal that FP tries to achieve.

Better than the rest

There’s so much more to say about FP, but that wouldn’t help us to jump right into using it. Some of the FP languages are already notorious of overwhelming their potential users with jargon only usable to scare humans away. Some would strongly disagree on the limited set of FP features I presented above, and I would agree with them, but as I said, I’m trying to approach from a practical perspective and trying to encourage using FP as soon as possible, and for that I only provide the necessary minimum amount of information. While some features might be relevant only in comparing FP languages, other features will indeed be important later when we start using them. For that we don’t need to know the history and research behind those features, it’s enough to mention the problems they solve, and even those we can postpone until we actually use them. Or beyond. We can and we do actually use some of FP’s inventions without ever knowing that they are called monads, and that they obey to some laws.

Trying to answer the question ‘what makes a monad?’ happens to be an overkill when it comes to just using a monad. We will see soon that using the basic and more advanced FP concepts can be a joy, can simplify code greatly, and catching up with them is considerable less of a challenge than understanding the theory behind them. So don’t worry about these terms for now, we will get back to them right after we started using them.

In the next part we will start using the concepts introduced here, and I will talk about currying, what’s the buzz around it, and how it can help us to write cleaner code, so stay tuned!

--

--

Viktor Daróczi

A software engineer focusing on the fun part of computing.