Point-Free or Die: Part 1
This is the first post in a series about tacit programming and point-free syntax in Haskell and other languages. Be sure to check out Part 2!
For an initial understanding of tacit programming, consider that a synonym for tacit is quiet and an antonym is noisy. Code is noisy when it contains more information than we need to see. It’s quiet…when it doesn’t.
One way of producing tacit code is to write function definitions in a point-free style. As opposed to a point-ful definition, a point-free definition is tacit because it tells you about the function without mentioning one or more of its arguments.
Here is an example of a point-ful definition and its point-free equivalent:
lengths ls = map length ls
lengths = map lengths
They mean the same thing. Both definitions have this type:
lengths :: Foldable t => [t a] -> [Int]
In other words, lengths
receives a list of foldable structures containing values of some generic type and returns a list of Int
. That, is the list of lengths.
With differing syntax, the point-ful and point-free definitions communicate differently:
lengths
receives a list of items and maps each to itslength
.
vs.
lengths
is a map oflength
.
Consider the succinctness of the second version. It’s shorter and quicker to say, but understanding it requires that you know how map
acts on the elements of a list.
Calibrating Abstraction
Here’s another example:
sum xs = foldr (+) 0 xs
“sum
is a function that iterates over xs
to add up its elements, starting from zero and proceeding from right to left.”
Or you could say:
sum = foldr (+) 0
sum
is a right-fold of addition starting from zero.
When we leave out the xs
, we communicate at a higher level of abstraction. We are no longer talking about what sum
does, but what it is: a fold with certain properties.
Let’s combine these two functions to make a new one:
totalNumber ls = foldr (+) 0 (map length ls)
By substituting in our definitions for sum
and lengths
, we get
totalNumber ls = sum (lengths ls)
And of course, we can make this look a little more like Haskell by using $
.
totalNumber ls = sum $ lengths ls
You could say it’s quieter with the $
, but it’s not point-free yet. totalNumber
is defined in relation to its argument ls
: “totalNumber
receives a list of foldable structures and returns the sum
of the lengths
of those structures.”
But then, you could also just say, “totalNumber
is the composition of sum
and lengths
.” How do we get there?
totalNumber ls = sum $ lengths ls
totalNumber ls = sum . lengths $ ls
totalNumber = \ls -> sum . lengths $ ls
totalNumber = sum . lengths
This is the process of eta-reduction: converting a point-ful definition to a point-free one. Here it takes three steps:
- First, we notice that applying
sum
tolengths ls
is equivalent to applying the compositionsum . lengths
directly tols
. - Next, we consider that
totalNumber
is a function that receivesls
and simply applies an expression to it. - Finally, we drop the unnecessary lambda, removing
ls
from the definition.
Eta-reduction, also called eta-conversion is a simply mechanical process–which means computers can do it!
In fact, you don’t have to do any of your eta-reduction yourself. Haskell’s pointfree
library can do it for you!