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:
lengthsreceives a list of items and maps each to itslength.
vs.
lengthsis 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
sumis 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
sumtolengths lsis equivalent to applying the compositionsum . lengthsdirectly tols. - Next, we consider that
totalNumberis a function that receiveslsand simply applies an expression to it. - Finally, we drop the unnecessary lambda, removing
lsfrom 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!