Functional Programming for WordPress Developers Part 2
In part 1, I introduced the basic concepts of Functional Programming. In part 2, we cover first-class functions and point-free style.
First class functions
PHP 5.3 introduced anonymous functions, and more importantly made functions a first class datatype like strings and integers. They can be assigned to variables, passed as parameters to functions, and returned as values.
If you’re used to programming in other languages, right now you’re probably cursing whoever decided PHP variable names start with a dollar sign. In other languages, “function absint” and “absint = function() …” both create something named “absint” for you to call. But in PHP, we have to live with that weird bit of syntax that makes our functions as first-class datatypes look like second-class citizens.
Other than that awkwardness, anonymous functions that we assign to variables work exactly like regular, named functions.
Functions from functions
Remember absint(), my favorite WordPress function?
absint() takes a single parameter and calls two other functions to get its result. You could call absint() a composition of those two functions. Functional programmers express this relationship with the compose() function.
compose() takes a list of functions and returns a function that calls them all in turn, from right to left. The only catch is that each function passed to compose() can only take one parameter. Instead of writing an absint() function, we could declare how its composed. That’s less code for us to write, and fewer chances for something to go wrong.
My high school geometry teacher and I had a bit of culture shock when it came to functions. In my programmer’s world, functions can take multiple parameters. Take WordPress’s add_filter() for example. add_filter() takes the name of a filter hook and a function to implement it. But in high school math, all of our functions were univariate. They only took one parameter. There’s no f(x,y).
Univariate functions are simpler to write and reason about. They’re also easier to compose into new functions. So, functional programmers have come up with ways of decreasing functions’ parameter counts. We partially apply parameters to a function to create more specific versions of it. If we apply all but one of a function’s parameters ahead of time, we can use that function when we compose another one!
Many purely-functional languages curry functions automatically. When a function is curried, you call it repeatedly with one parameter at a time until all of the required parameters have been accounted for. In PHP, we have to explicitly curry() a function, making curry() just a different way to partially apply parameters.
The term originated in topology, a branch of mathematics which works with spaces composed of points, and functions between those spaces. So a ‘points-free’ definition of a function is one which does not explicitly mention the points (values) of the space on which the function acts. — The Haskell Wiki
Point-free style, or Tacit Programming, is a declarative style of coding. Point-free programmers challenge themselves to write as few actual functions as possible, instead declaring functions as compositions of existing functions and partially applying parameters when necessary. By working with simple building blocks we know and trust, it’s easier to trust the resulting functionality.
Think of point-free code like a pipeline you feed data through. It doesn’t matter whether water, milk, or sewage flows through that pipeline, because the individual pieces of pipe never address the data point by name.
Here’s an example of a WordPress plugin written in a point-free style. Notice how only one function is defined. The rest of the functionality is built by partially applying and composing functions from PHP and the WordPress API.
Trade-offs & memoization
All these function calls and dynamic function declarations add overhead to your code. The functional style costs execution time and memory, although not as much as you’d think. It’s the price we pay for expressing logic in this more mathematical manner.
One way to compensate for this is to explicitly trade memory for CPU time through memoization. The memoize() function takes a Referentially Transparent function, which you may remember means it always returns the same result for the same parameters. It returns a new function that does the exact same thing but caches its results. Call it a second time with the same values and it’ll return the same result from memory. This is useful when a function takes a long time to run, but you should benchmark your code with memoization and without to make sure it’s actually helping.
Also, keep in mind there are trade-offs we can’t easily quantify. Code written in a functional style doesn’t look like what most developers expect. Debugging Point-Free code is especially confusing if you’re not used to that way of thinking. If you work with other developers, don’t rush to use these techniques in your code. Share your interest with your team and decide together if this is something you agree to pursue.
You’ve probably heard functional programmers talk about monads. We’ll look at some sample monads in part 3.