December 05, 2007

More on Point-Free Programming Style

I discovered that programming in Point-Free Style can be addictive! Today, I wrote two perverse functions using PFS before discovering I had spent almost two hours just two add two lines to my code!!!

Here's the one of the two:

foo = uncurry (.) . (map . second . drop . length &&& (filter . (flip $ flip isPrefixOf . snd)))

Sorry if it didn't fit in your screen. I used the name foo purposedly: can you imagine what this function does?

If you can't come up with a solution in less than 1 minute, the above code is a failure as it means nothing to you (i.e. its meaning is not apparent).

Try with the following version:

foo p [] = []
foo p ( (k,l):os ) | p `isPrefixOf` l = (k,take (length p) l) : foo p os
| otherwise = foo p os

Sorry for the bad layout.

It took two hours to me to figure out how and where to put all those . and $ in the first version but it was fun, just like winning a game.

On the other hand, I wrote the second version as I was writing this post. I think the reason why I write PFS code much much slower lies both in my lack of knowledge of PFS programming and my way of reasoning about programs.

Nevertheless, PFS is appealing to me because I'm used to write shell scripts to automate things on the systems I manage: PFS is all about composing and piping functions, mostly like you compose and pipe shell programs.

I wonder if I'll be able to guess the meaning of the PFS functions I wrote after some months: we'll see.

As a side note, I noticed that the following pattern is coming up rather often when coding in FPS:

goo = uncurry g . (foo &&& bar)

which means:

goo i = foo i `g` bar i

So, I wonder whether the following combinator would help to making FPS code more readable:

f `interspersedBy` g = uncurry g . f

So, the above snippet would become:

goo = (foo &&& bar) `interspersedBy` g

3 comments:

Joe Fredette said...

you can use the "pre" tag to preserve spacing.

Daniel Wagner said...

Hiya! Note that there's some slightly pointier code that might be more readable to you.

foo p = map (second . drop . length $ p) . filter (isPrefixOf p . snd)

This still acknowledges the intent of point-free coding. In particular, the idea of point-free coding is to think in terms of composing functions and in terms of data flow, rather than in terms of order of operations. This code is, indeed, the composition of two higher-order functions, and so I would say it matches the Haskell philosophy nicely. The difference is that I find this version quite readable!

As a side note, you may also be interested in the function "liftM2", which does just what "goo" does above.

~d

Cristiano Paris said...

Thank you Daniel!