Introduction

In recent years, functional programming has become increasingly popular as a concept. Even in “non-functional” programming languages, the concepts and practices of functional programming have become more mainstream.

Why is that? Are they actually useful, or is functional programming in a language other than Haskell just trying to fit a square peg into a round hole? What even is functional programming, and does it make sense in PHP?

This book aims to answer those questions. (Spoiler alert: The answer is yes.) While writing PHP as if it were Haskell, ML, or LISP is not going to produce a good result, the underlying principles behind functional programming offer many advantages, even in PHP. Other parts, however, do not, and that’s okay.

The aim of this book is not to convince you to write all PHP code in a strictly and rigidly functional fashion. Rather, the aim is to encourage you to approach problems from a functional mindset and apply the underlying principles of functional programming… most of the time. Many of them, in fact, you have no doubt encountered before in other contexts talking about “good code” under different names, such as the SOLID principles. That’s a good sign the concepts involved are a good idea.

Functional vs. Procedural

There are many different paradigms for conceptualizing computation and programming, but the two oldest and most widely applicable are procedural programming and functional programming. Both were developed in the 1930s very close together, and have been shown to be equivalent; that is, there is no programming problem that can be solved in a procedural approach that cannot also be solved in a functional approach, and vice versa. One paradigm may be easier to write, or easier to read, or easier to think about, or more performant, or require fewer lines of code. Which one has the “edge” varies widely from one situation to another, but if it’s possible to solve in one, then it’s possible to solve in the other.

One could argue that since both are equally capable, a programmer needs to learn only one to solve any solvable problem. That would be a short-sighted view. By that logic, one would need to learn only a single Turing-complete language and be done with it, since all Turing-complete languages are, by definition, equally capable.

While it’s trite to fall back on cliches like “use the right tool for the job,” it’s a cliche because it’s true. But being able to determine the right tool for the job requires being at least competent in multiple tools. In the case of programming, that means being at least competent in multiple programming languages as well as multiple programming paradigms, if only to know when to not use a particular one.

Most programming resources, however, are geared toward a procedural mindset. That’s not surprising. It is the approach easiest to explain to a new developer, and at the end of the day, nearly all computer hardware in existence is procedural; anything else is simply syntactic sugar on top of assembly. That is unfortunate, though, as procedural is also the paradigm that scales least-well to large-scale systems, concurrency, and the ugly complex mess that is 21st-century computing.

What functional programming and procedural programming share, at least in their modern forms, is the division of programs into separate logical chunks that can be mixed and matched and reused. Even if the syntax is often similar or identical, however, functional programming goes a step further and makes those logical chunks first-class citizens that can be dynamically created, modified, passed around, even stored and reused. We’ll see concrete examples of that in Part One.

Object-Oriented Code

The dominant programming paradigm today is neither functional nor procedural, but object-oriented programming (OOP)—at least, that’s the conventional wisdom. One issue with said conventional wisdom is OOP is a very loosey-goosey paradigm, with several sub-paradigms that actually get used. Far and away, the most common is what I term “Classic OOP,” as in, OOP that’s based in classes. C++, Java, C#, and PHP are all in this family, and with the introduction of explicit classes in ECMAScript 6 JavaScript sort of is.

There are other variants of OOP, however, some of which do not even include the quintessential “inheritance” concept. In practice, most OOP code in the wild is what I would term “procedural OOP,” that is, procedural thinking wrapped up in OOP style. That is not necessarily a bad thing; it is the dominant model today, so it must be doing something right. However, it is not fully message-centric OOP (a la Smalltalk). It also means there really is such a thing as “functional OOP,” or “object-functional” code.

Contrary to the Reddit consensus, OOP and functional programming are not adversaries that force you to choose One True Code Paradigm. Rather, we can and should leverage the lessons of both to produce the best solution to the problem.

Functional Style? Functional Language?

One difficulty in understanding functional programming is that, much like object-oriented programming, there is no one clear definition of functional programming. On one level, it’s simply “programming with functions the way math does,” which is a nice and uninteresting definition. However, doing so quickly leads down a rabbit hole of category theory, monoids, endofunctors, generic type systems, pathological recursion, and other weird sounding words and phrases that are prone to turn off even the most receptive audience. In many cases it ends up looking like an unintentional Mott-and-Bailey fallacy, an informal logical fallacy where one argues two related points at the same time, one controversial and one not, and switches between the two depending on which is more advantageous in the moment.

There is, really, no universal definition of functional programming, nor of a functional programming language. If “functional programming language” means “a language in which one can do functional programming,” then nearly all modern languages are functional; this is not a helpful definition.

A better definition is to distinguish functional-style programming from a functional language.

  • Functional-style programming is a particular way of approaching a logical problem in code. It includes a couple of fairly simple core concepts, plus a huge array of concepts that naturally flow from those. Most of those are not all-or-nothing, making it possible to write “functional-ish” code in almost any modern language even if the syntax may end up a bit clunky.
  • A functional language is a language whose syntax and semantics are geared toward making functional-style programming easier and more natural, often (though not always) at the expense of other paradigms. Often those functional concepts are baked-in assumptions in the language, and if you are not aware of those assumptions, you’re likely to get bitten.
  • A strictly functional language is one whose syntax and semantics are geared toward writing in a functional style only, period. These languages go all the way down the rabbit hole and make thinking about a problem any other way frequently impossible. They also have a reputation for being impenetrable, precisely because they only support functional models.

PHP, in particular, is a language optimized for procedural OOP; it supports classic OOP, and that is the code style most widely used, but the single-threaded, shared-nothing architecture is intrinsically procedural. Since PHP 5.3, however, it has had syntactic support for functional-style programming. PHP 7.4’s introduction of short-lambdas further simplifies it far enough that functional-style code is now entirely viable.

That said, would I advocate writing 100% purely functional code in PHP? Not really. Many of functional code’s advantages do not actually apply in a single-threaded, shared-nothing environment, and others will work but collide with other limitations of the language to make them less friendly to work with.

Type Systems

Type systems are a tricky topic in functional programming. On the one hand, type systems are entirely orthogonal and unrelated to functional programming. Some functional languages have extremely robust type systems (e.g., Haskell), while others have seemingly none at all (LISP). Yet, they’re often talked about in the context of functional programming. Why?

The answer lies in the previous observation that functional languages tend to have either no type system or a very robust one. Rarely have I seen a mediocre functional type system. While functional programming itself does not in any way require having an explicit type system, in practice functional code encourages and requires a very robust type system. A weak type system often cannot handle the complexity that a functional code base necessitates.

PHP is unusual in this case in that it has a relatively weak type system (compared to what languages like Rust or Haskell or even modern Java offer), but it’s all opt-in. As we’ll see, the result is that taking a fully functional approach will, in some cases, lose us some explicit typing ability. I don’t see that as a fatal problem, but rather as an indication of what sort of type system improvements PHP should prioritize in the future.

We’ll cover some typing concepts along the way, as necessary, but this is not primarily a book about typing.

What to Expect

This book is divided into four parts:

  • In Part One, we will examine the fundamental principles of functional programming as a concept, using PHP as the example language. The results will not always be syntactically pretty, but that’s not the goal; understanding the value of the principles is, and we’ll use concrete PHP examples to go a little way down the rabbit hole.
  • In Part Two, we’ll step back and look at the theory that underpins much functional programming, specifically category theory. This is not a book on category theory, but a cursory understanding of the basics will greatly help to explain how functional programming and other formalisms really do make for more robust code.
  • In Part Three, we’ll bring the theory and practice together to demonstrate some more advanced techniques that make the theory concrete and make for more robust code at the same time.
  • In part Four, we’ll step back and consider the broader picture of what else functional programming could be, and where it should sit in your toolbox.