

Note on point-free programming style

This old comp.lang.functional article by Albert Y. C. Lai, makes the point that Unix shell pipeline programming is done in an essentially "point-free" style, using the shell example: grep '^X-Spam-Level' | sort | uniq | wc -l and the analogous Haskell code: length . nub . sort . filter (isPrefixOf "X-Spam-Level") Neither one explicitly mentions its argument, which is why this is "point-free". In "point-free" programming, instead of defining a function in terms of its effect on its arguments, one defines it by composing the component functions themselves, directly, with higher-order operators. For example, instead of: foo x y = 2 * x + y one has, in point-free style: foo = (+) . (2 *) where (2 *) is the function that doubles its argument, and (+) is the (curried) addition function. The two definitions of foo are entirely equivalent. As the two examples should make clear, point-free style is sometimes natural, and sometimes not, and the example chosen by M. Lai was carefully selected to bias the argument in favor of point-free style. Often, after writing a function in pointful style, I get the computer to convert it automatically to point-free style, just to see what it looks like. This is usually educational, and sometimes I use the computed point-free definition instead. As I get better at understanding point-free programming style in Haskell, I am more and more likely to write certain functions point-free in the first place. For example, I recently wrote: soln = int 1 (srt (add one (neg (sqr soln)))) and then scratched my head, erased it, and replaced it with the equivalent: soln = int 1 ((srt . (add one) . neg . sqr) soln) I could have factored out the int 1 too: soln = (int 1 . srt . add one . neg . sqr) soln I could even have removed soln from the right-hand side: soln = fix (int 1 . srt . add one . neg . sqr) but I am not yet a perfect sage. Sometimes I opt for an intermediate form, one in which some of the arguments are explicit and some are implicit. For example, as an exercise I wrote a function numOccurrences which takes a value and a list and counts the number of times the value occurs in the list. A straightforward and conventional implementation is: numOccurrences x [] = 0 numOccurrences x (y:ys) = if (x == y) then 1 + rest else rest where rest = numOccurrences x ys but the partially point-free version I wrote was much better: numOccurrences x = length . filter (== x) Once you see this, it's easy to go back to a fully pointful version: numOccurrences x y = length (filter (== x) y) Or you can go the other way, to a point-free version: numOccurrences = (length .) . filter . (==) which I find confusing. Anyway, the point of this note is not to argue that the point-free style is better or worse than the pointful style. Sometimes I use the one, and sometimes the other. I just want to point out that the argument made by M. Lai is deceptive, because of the choice of examples. As an equally biased counterexample, consider: bar x = x*x + 2*x + 1 which the automatic converter informs me can be written in point-free style as: bar = (1 +) . ap ((+) . join (*)) (2 *) Perusal of this example will reveal much to the attentive reader, including the definitions of join and ap . But I don't think many people would argue that it is an improvement on the original. (Maybe I'm wrong, and people would argue that it was an improvement. I won't know for sure until I have more experience.) For some sort of balance, here is another example where I think the point-free version is at least as good as the pointful version: a recent comment on Reddit suggested a >>> operator that composes functions just like the . operator, but in the other order, so that: f >>> g = g . f or, if you prefer: (>>>) f g x = g(f(x)) The point-free definition of >>> is: (>>>) = flip (.) where the flip operator takes a function of two arguments and makes a new function that does the same thing, but with the arguments in the opposite order. Whatever your feelings about point-free style, it is undeniable that the point-free definition makes perfectly clear that >>> is nothing but . with its arguments in reverse order.

[Other articles in category /prog/haskell] permanent link

