8

So I keep hearing a lot about point free programming and I decided to do a little experiment to test my grasp of it. This involved taking a pointed function to calculate the factorial of a number and converting it to a point-free form. I managed to do it, but the point free result is a lot less readable than the pointed result.

-- pointed
fact 0 = 1
fact n = n * (fact (n-1))
-- point free
fact' = foldr1 (*) . takeWhile ((<) 0) . iterate (flip (-) 1)

Am I missing something essential to point free notation or is this as readable as certain transformations get? To me it seems that a big part of the fact function is the pattern match on zero, and indeed, pattern matching is one of the biggest reasons I love Haskell. However point free notation seems to completely disallow that, along with some other things that are extremely useful like list comprehensions.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Dwilson
  • 1,239
  • 9
  • 18
  • 6
    I prefer `product . enumFromTo 1`. – Vitus Jul 19 '12 at 03:16
  • 5
    [Evolution of a Haskell programmer](http://www.willamette.edu/~fruehr/haskell/evolution.html) – sdcvvc Jul 19 '12 at 11:04
  • 2
    Wait. You expected _readability_ from _point-free syntax_? Ha. Haha. Hahahahaha. No. – Louis Wasserman Jul 19 '12 at 12:11
  • For this example, as well as the point free version being a lot less clear, it would also take a very good compiler to get the point free version to be anywhere near as efficient as the direct version. – stephen tetley Jul 19 '12 at 16:23
  • You're not going to use point-free notation for everything. Some functions are expressed very naturally as sequences of function compositions, and some functions are not. Don't go out of your way looking for ways to make code point-free just because the cool kids talk about it; use it when it seems like the most natural and direct way to express your intent. – Chris Martin Feb 01 '18 at 22:44

2 Answers2

15

The canonical factorial in pointfree form is:

fact = product . enumFromTo 1

(which is equivalent to fact n = product [1..n])

I find this to be pretty readable. However, I would concur that the original version:

fact 0 = 1
fact n = n * (fact (n-1))

Matches the definition very well and is also readable.

The point (ha!) of pointfree form is to make it easy to reason about functions as the composition of other functions. However, the factorial function isn't really an excellent candidate for this kind of reasoning.

The decision is yours, obviously.

John Gietzen
  • 48,783
  • 32
  • 145
  • 190
  • Interesting, I didn't know a function like enumFromTo existed. So what about pattern matching? By necessity that requires pointed notation. Would you argue that that doesn't fall under the bounds of reasoning about functions as composition? – Dwilson Jul 19 '12 at 03:27
  • @Dwilson: it's part of the `Enum` typeclass: http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#t:Enum – Riccardo T. Jul 19 '12 at 07:31
  • 3
    @Dwilson: You can translate pattern matching into point-free notation. Just look at functions like `maybe` or `either`; taking `either` as an example: you give it two functions, one for `Left` and second for `Right` case and that's it. In fact, you can quite easily construct those functions because they are part of the isomorphism between ADT and its Church-encoding. Lists probably don't have such function, but it's easy to implement: `listcase [] z f = z; listcase (x:xs) z f = f x xs`. – Vitus Jul 19 '12 at 13:19
  • 2
    @Dwilson: the `enum*` functions power haskell's sugary `[1.. 5]`, etc. enumeration syntax – jberryman Jul 19 '12 at 15:33
2

For each algebraic union data type there should exist its type case discriminator function which encapsulates the pattern matching for that type. We already have

either :: (a -> c) -> (b -> c) -> Either a b -> c
maybe :: b -> (a -> b) -> Maybe a -> b

Similarly there must be such function for numbers,

num :: (Num a) => b -> (a -> b) -> a -> b
num z nz 0 = z
num z nz x = nz x

so we can write

import Control.Applicative
import Data.Function

fact :: (Num a) => a -> a
fact x = num 1 (\x-> (*) (fact (pred x)) x) x
       = num 1 ((*) =<< (fact.pred)) x

i.e.

fact   = (num 1 . ((*) =<<) . (. pred)) fact
       = fix (num 1 . ((*) =<<) . (. pred)) 
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 1
    @leftaroundabout was there a `:)` somewhere perhaps?.... :) the main point I wanted to make was actually with the `num` function. but also, to respond to the title more closely - it's not about the `factorial` per se... – Will Ness Nov 11 '13 at 10:52
  • While certainly less readable, this is interesting. Could you elaborate a little more on the first sentence and what `num` means in regards to that, Specifically the type case discriminator function part? – Dwilson Nov 12 '13 at 05:01
  • 1
    @Dwilson I mean something to encapsulate the pattern matching on a given algebraic union data type, i.e. one with cases, like `data T = A | B | C x | D y z | ...`. Any such type is a glorified pair (a,b) anyway, and we do accept `fst` and `snd` as point-free combinators. So it seems logical to me to allow such accessors for any such type. Just as we have `either` & `maybe`, 2discriminate among the various cases of a given type (`Either a b`, `Maybe a`) and take appropriate action in each case, so we can have it 4pattern match on Zero or NotZero for Numbers, for instance. Is what I meant. :) – Will Ness Nov 12 '13 at 08:07
  • @Dwilson in the same venue we'd have e.g. for lists something as `list n nn [] = n; list n nn xs = nn xs` or for Booleans `bool t f True = t; bool t f False = f`. o I'm saying that whenever new union type is introduced, it is reasonable to expect/demand the existence of such case discriminator. Of course it's only strictly needed in pointfree code; in regular code we get the pattern matching abilities of Haskell, for free, for this new type. – Will Ness Nov 12 '13 at 08:10