Table of Contents
Have you ever heard about “Tacit programming” or “Pointfree style programming”? Have you ever come across code snippets like the example below and wondered how they could work, and what parameters are being passed in?
Pointfree style will help you write better code, by letting you and future maintainers focus on functions and their meanings, rather than on minor details. In this article, we’ll focus on this style of programming, and its advantages — as well as its possible disadvantages! Thus, this article stays in the trend of applying functional programming techniques to enhance your code.
Even though using this style is not essential to writing code in a functional way, understanding jargon like this is useful in discussions and when trying to understand someone else’s code.
Table of Contents
What is Pointfree Style?
You can think of points as “function arguments that are explicitly referred to”. What is point-free style? It means you’re programming in a way that you don’t need to name your arguments or name any of your intermediate values. When you define a function, normally, you have to name the arguments. Most languages require it, or they make that a really easy way to define a function.
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.
Likewise, when you’re in the body of the function, you’re writing what this function does. Often the easiest way to use the result of calling a function is to save it to a variable, and then pass that variable to the next function. Use it later in the expression.
The easiest example to really hit home is, let’s say you’re calling map, and you’re calling map with something like toUpperCase, the function toUpperCase. On a list of strings, you’re mapping toUpperCase over those strings.
How could you do this with Pointfree style? In all cases, we didn’t specify the arguments to the functions; we used named functions instead of something like .map(str => toUpperCase(str)). In this example, the “str” variable in line 7 is a point.
We create an inline function there that accepts one argument and passes it, name its parameter “str”, and use it when invoking toUpperCase just a few characters later. We name and reference “str” explicitly, so “str” is a point.
You don’t have to name any of those intermediate values. You don’t want to be thinking about, “How do I name this function?” or “How do I name this argument?” Stuff like that. You want to be thinking higher order like, “OK. This is the composition of this. This is mapping over this.” That’s what you’re thinking. If you can avoid it, and without much cost, it might be worth avoiding naming. The names take up more room, so you have longer code.
Later, the function is modified, but the variable name remains the same. If you forget to update the variable name everywhere it’s used, it can lead to confusion and bugs in the code. Naming is hard, you could have avoided that maybe, and not have to deal with it, at all.
Explicitly referring to function parameters can indeed make debugging easier. When all parameters are explicitly logged, it provides a clear and immediate insight into the state of the system at a particular point in the code.
This kind of debugging output is particularly helpful when you need to trace the flow of data through a function and understand how the parameters contribute to the result. It provides transparency and facilitates the identification of any issues related to the input parameters.
Watch out though!
Rewriting code to a point-free style is not always that easy — sometimes to go point-free, we need to transform the inner function so that its signature matches the outer function. This sometimes requires us to use more complex techniques.
Let’s take a look at this example:
Why is this happening? 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. This breaks because “map” passes more than just one argument to “parseInt” – it passes “currentValue”, “index”, and “array”. “parseInt” then accepts the second argument as “radix”.
In this case using an arrow function would have been correct:
Or we could use a higher order function to turn the given function into a unary one. In this case, we need to make sure that “parseInt” receives only the first argument, and ignores the rest:
Compose and pipe are functional programming concepts that allow you to transform functions with multiple expressions and assignments into more concise, point-free versions.
Suppose you have a function that get name from object then return the formatted name to uppercase:
Instead of writing a new function that takes x, calls g of x, and then passes that to f and returns it. You just write, Compose f g. You’ve eliminated the argument. Function composition is used a lot in point-free style. Another way that’s very related to function composition is pipelining.
Now, let’s use pipe from a functional programming library (like Ramda.js or lodash) to create a point-free version of this function:
What point-free style allows you to do is write relatively long function composition expressions in a top-down manner, one line at a time. The return value of one operation is seamlessly passed to the next, and so forth. The advantage is that you don’t have to name intermediate values, contributing to achieving a point-free style.
One of the obvious downsides to point-free style is that it can get us into readability issues if we are taking it to the extreme. While it serves as a good exercise and encourages thinking at a higher-order function level, it doesn’t necessarily mean the code you produce is optimal for long-term maintainability in a system.
Point-free style is often more practical with a good type system. When you eliminate arguments and intermediate value names, you lose valuable information. Without a good type system, it can become challenging to determine the correctness of the function or identify mistakes when reviewing the code.
I genuinely believe that point-free style is helpful in making the projects’ main logic cleaner and more condensed. But this benefit comes at an expense or at least with some cautions.
The choice to use point-free style is indeed a matter of coding style and personal preference, and it’s not a requirement for writing functional code. Point-free style can be beneficial in terms of readability and conciseness, but it might not be suitable for every situation or for every developer, it can be jarring if done in excess.
Ultimately, the choice between point-free and pointful style depends on the preferences of the developer and the specific context of the code being written. Striking a balance between clarity, readability, and conciseness is crucial in writing maintainable and understandable code.