What is Pointfree Style?
Pointfree style has to do with not needing to specify the arguments for each function application. The term “point” stands for a function parameter, and pointfree has to do with not naming such parameters. In this paradigm we want the reader to focus on what functions are, not caring about the specific names of its parameters.
Pointfree style is also known as tacit programming by its champions, or as Pointless Programming by its detractors!
This style is most often seen when composing or pipelining functions, common patterns in Functional Programming. The idea is to work at a higher level of abstraction, focusing on what functions do, and avoiding minor details such as what parameters are called. If there’s less code to read, you’ll be able to understand it quicker… Although, if ill-used or taken to extremes, pointfree style may end up making code more obscure instead!
Let’s see an actual example so we can get a taste of pointfree style!
Monsieur Jourdain, a character in Molière’s play ”Le Bourgeois Gentilhomme” (“The Bourgeois Gentleman”) was quite surprised to learn that he had been speaking in prose for more than 40 years without even knowing it. You, the reader of this series of articles, may have not noticed that we already used pointfree style before!
1const testOdd = x => x % 2 === 1;2const testUnderFifty = x => x < 50;3const duplicate = x => x + x;4const addThree = x => x + 3;
We applied those operations sequentially to an array. First, we dropped even numbers; then, we doubled the remaining ones; after that, we dropped results that were 50 or more, and finally, we added 3 to whatever was left.
1const myArray = [22, 9, 60, 24, 11, 63];23const a0 = myArray4 .filter(testOdd)5 .map(duplicate)6 .filter(testUnderFifty)7 .map(addThree);8// [ 21, 25 ]
In all cases, we didn’t specify the arguments to the functions; we used named functions instead of something like
.filter(x => x % 2 === 1) — this is pointfree style! The resulting code is more compact, and simpler to read: “keep only odd numbers, duplicate all, keep numbers under 50, finally add 3 to all”.
With pointfree style, all you see are function names, with little “extra-JS” things. This style achieves brevity, but you must endeavor to pick good, readily understandable function names. (A famous saying, “There are only two hard things in Computer Science: cache invalidation and naming things” comes to mind…) Applying this style, you will end up with two kinds of functions: short ones with a single task and a clear name, and longer ones that coordinate other functions, using pointfree style for readability.
Let’s see another example, a (slightly) more realistic one. Suppose you have to transform a title into ”title case” — or at least a simplified version of it, in which all words have their first word capitalized (in actual title case format some words don’t get their first letter capitalized unless at the start of the title). We can do this in pointfree style by using a few functions. First, let’s see a couple of standard functional programming tools: pipelining and mapping.
1const pipeline = (...fns) => fns.reduce((result, f) => (...args) => f(result(...args)));23const map = fn => arr => arr.map(fn);
Pipelining (for which some proposals are being considered at this time) just applies the first function to a parameter, then passes the result to the second function, then that result to the third function, etc. As in Linux and Unix, a pipe gets some input, processes it, and passes it on to the next function in the pipeline. Our
map(...) function is a curried version of the
.map(...) method, more suitable for functional programming purposes.
Now, having these tools, let’s work on making a string in title case.
1const separateStringIntoWords = (str) => str.split(" ");23const firstToUpper = (str) => str.toUpperCase() + str.slice(1).toLowerCase();45const joinWordsIntoString = arr => arr.join(" ");67const makeTitleCase = pipeline(separateStringIntoWords, map(firstToUpper), joinWordsIntoString);
To convert a title into title case you just use
makeTitleCase(...) as follows.
1makeTitleCase("GO TO sTaTeMeNt conSIDered HARMful");2// "Go To Statement Considered Harmful"
You may write this more succinctly, even as a “one-liner” — but is it clearer?
1const makeTitleCase2 = (str) => str.split(" ").map(word => word.toUpperCase() + word.slice(1).toLowerCase()).join(" ");2makeTitleCase2("GO TO sTaTeMeNt conSIDered HARMful");3// "Go To Statement Considered Harmful"
Let’s go further into this. Suppose you want to debug the title casing function. As is,
makeTitleCase(...) can be seen as correct by understanding that you apply three functions: one that (very likely!) takes a string and separates into words, then a second one that maps each word by making its first letter uppercase, and a final one that makes a string out of several words. Those auxiliary functions will probably be used elsewhere, so that’s not a waste. And, I believe that the extra clarity compensates the possibly longer code.
Open Source Session Replay
Debugging a web application in production may be challenging and time-consuming. OpenReplay is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue. It’s like having your browser’s inspector open while looking over your user’s shoulder. OpenReplay is the only open-source alternative currently available.
Happy debugging, for modern frontend teams - Start monitoring your web app for free.
1const numbers = [22,9,60,12,4,56]2numbers.map(Number.parseFloat); // [22, 9, 60, 12, 4, 56]3numbers.map(Number.parseInt); // [22, NaN, NaN, 5, NaN, NaN]
Why is this happening? The key is that Number.parseFloat(…) receives a single argument —the string to parse— while Number.parseInt(…) allows a second optional argument, the radix for the number. The array.map(…) method calls whatever function you provide with three arguments; Number.parseFloat(…) ignores the two extra ones, but Number.parseInt(…) takes the second one to be a radix, and all problems ensue. In this case using an arrow function would have been correct:
1numbers.map((x) => parseInt(x)); // [22, 9, 60, 12, 4, 56]
When you use functions with extra (and, as here, unwanted arguments) Functional Programming provides another solution. We could use a higher order function to turn the given function into a unary one.
1const unary = fn => (...args) => fn(args);
Now, our parsing problem goes away.
1numbers.map(unary(Number.parseInt)); // [22, 9, 60, 12, 4, 56]
Whenever you use pointfree style with functions that you didn’t write yourself, you expose yourself to this sort of bug; be careful!