Point-Free or Die: Part 1

Posted on August 6, 2016

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 its length.

vs.

lengths is a map of length.

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:

  1. First, we notice that applying sum to lengths ls is equivalent to applying the composition sum . lengths directly to ls.
  2. Next, we consider that totalNumber is a function that receives ls and simply applies an expression to it.
  3. 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!