Just Enough FP: Pointfree
Pointfree programming is a style of programming free of points. Great, you got it. Blog post over.
Just kidding!
While my first sentence is true, it’s pretty unhelpful, so let me explain what pointfree programming is a little bit better.
In order to explain pointfree, we first need to understand what a “point” is. I’m going to teach you by showing you, and I’ll use the Array.prototype.map
method to demonstrate that.
The map
method is a higher order function (because it takes a function as an argument). You can read the docs here. Let’s create a simple example:
const arr = [1, 2, 3, 4, 5]
const doubles = arr.map(x => x * 2)
console.log(doubles) // [2, 4, 6, 8, 10]
Alright, really simple example. We pass a function to the map
method that multiplies the number by 2
and we get a new array with doubled numbers.
I’d like to draw your attention to the function passed to map
, x => x * 2
. What is x
? Maybe a better question, why x
? Why not foo
, or num
, or potato
? Each would work equally well. What is it that x
signifies?
Another way of putting that is, what does x
point to?
The function we passed to map
is called a lambda, a single-use anonymous function. We use an arbitrary, interim variable to point to a value abstractly. Sure, we could follow “clean code” principles and name our interim variable better, but that would make it dependent upon the array, not the operation we’re doing to the array.
Another way to put this is that using lambdas in this way puts more focus on the data, and less on the functions transforming the data. We want to flip this, and as we’ll see when we get into composition, it’s necessary that we make this change.
There’s a way to use higher order functions that avoids lambdas, and increase legibility and reusability in our programs in the process. Let’s walk through that process.
The first thing we need to understand is that we can pass in the name of a function to map
, we don’t need to use a lambda. Our code can look like this:
const arr = [1, 2, 3, 4, 5]
const double = x => x * 2
const doubles = arr.map(double)
We have pulled our lambda out of the map
call and saved it in a variable as a function expression. We can now pass this variable directly to map
. This is pointfree programming. We no longer concern ourselves with the point in our higher order functions. Instead we make a simple, easily unit-tested (though you probably don’t need to) function, and pass that in by name.
Now, let’s take it a step further. Let’s combine currying and partial application to make this a little more functional.
const arr = [1, 2, 3, 4, 5]
const multiply = x => y => x * y
const multiplyBy2 = multiply(2)
const doubles = arr.map(multiplyBy2)
This time, we made a generic curried multiply
function. We apply the first argument to multiply
to create our multiplyBy2
function which is identical to our double
function from before. But this time, I can pass multiplyBy2
around my application for reusability.
”But Kyle, multiplying by 2 is really easy. Give me something with some more substance.”
I hear you. Let’s take a more complicated example and show why pulling the function out might be helpful. What if I want to slug-ify a list of strings? We can do that kinda functionally and using pointfree programming.
The steps in this algorithm are pretty simple: lower case the string and replace spaces with hyphens. Let’s create a bunch of random strings and give it a try.
const strings = [
'Portland summers are amazing',
'I like big beards and I cannot lie',
'I am out of ideas for strings', // ha
]
const slugs = strings.map(str => str.toLowerCase().split(' ').join('-'))
console.log(slugs)
// [
// "portland-summers-are-amazing",
// "i-like-big-beards-and-i-cannot-lie",
// "i-am-out-of-ideas-for-strings"
// ]
Let’s make this pointfree and break up the steps of our algorithm.
// Same strings as before
const lowerCase = str => str.toLowerCase()
const split = separator => str => str.split(separator)
const join = separator => arr => arr.join(separator)
const splitAtSpace = split(' ')
const joinWithHyphen = join('-')
// Bit of a crazy way to do it, but should still make sense
const slugs = strings.map(lowerCase).map(splitAtSpace).map(joinWithHyphen)
Now, I can read in plain English what’s going on in the final part of my program. Admittedly, it’s pretty verbose this way, so let me give you a sneak peek to the next blog post and do this magically with composition:
const slugify = compose(joinWithHyphen, splitAtSpace, lowerCase)
const slugs = strings.map(slugify)
Boom! 💥 We’re using pointfree all over the place and creating a brand new slugify
function in the process. I’ll explain this in greater detail in the next post, so stay tuned.
Conclusion
Pointfree programming gives us a few advantages. It encourages us to write small, easily tested, reusable functions to use throughout our applications. It encourages us to create well named functions so our programs become legible combinations of functions. And lastly, it reduces the surface area of bugs by eliminating lambdas and interim variables.
It will take some time to get used to, but with practice, reading pointfree programming will become as natural as reading the imperative pointful programming you’re probably doing today. Give it a try in practice, see if there are places you find it useful!
In this series, I’m going over material from my Just Enough Functional Programming in JavaScript course on egghead. This post is based on the lesson Eliminate Anonymous JavaScript Functions with Pointfree Programming.