Hacker News new | past | comments | ask | show | jobs | submit login
Point-Free style: What is it good for? (buffered.io)
36 points by bkudria on March 8, 2010 | hide | past | favorite | 22 comments



I just like point-free style because it leaves me with fewer things to name, and fewer choices to make. Placeholder names like "xs" are just noise. The "x" has no meaning here:

    double x = 2*x 
So I'd rather write it like this:

    double = (2*)
Similarly, there's no need to introduce an arbitrary name like "input" or "numbers" for this function (part of a Project Euler solution):

    highestProduct = maximum . map product . concat . map (groupsOf 4)


First I would like to say that I am only learning Haskell, and have done nothing but toy programs thus far. But the point free style is something that I really do not like about Haskell. It makes code much more complicated for the dubious benefit of saving a couple of keystrokes.

For example, take the code from the article:

This is without point free style: sum xs = foldr (+) 0 xs

And this is with: sum = foldr (+) 0

You have saved a total of 4 keystrokes. And made that line of code look really weird and hard to understand.

Thus, now when you look at code you cannot say at first glance how many arguments a function takes. The second line of code looks like sum takes 0 arguments. But you have to look more carefully at the definition of the function, and see that it uses foldr, and know that foldr takes three arguments, but here only two are defined, and then you figure out that someone is using point free style and then you figure out that sum, actually takes one argument that must be a list.

And this of course, is a simple example. Here we have a big clue in that the function is defined with zero arguments, and most functions take at least one argument, therefore the function probably uses the point free notation. But for a more complex and longer function, the point free style can very easily confuse someone reading the code.

So yeah, I think point free style is one of these annoying features of Haskell, that seem to be put in so that users of Haskell can pat themselves on the back and feel like they are very smart. But in the end it does not add much to the language and makes it much less accessible.


Once you're used to the idiom, I find point-free style at least as easy to read as pointful.

"Thus, now when you look at code you cannot say at first glance how many arguments a function takes."

In Haskell I generally look at the type signature - explicit or inferred - to see what a functions' arguments are.

I find this style is actually more beneficial for larger-scale examples than smaller ones. For example, if you have a memoizing combinator, most people would not find it odd at all to write:

  fast_factorial = memoize factorial
instead of:

  fast_factorial xs = (memoize factorial) xs
The key to point-free style is to think of all functions as potentially acting like combinators, and being modifiers for other functions.


And where do you find the inferred type signature?


Personally, I write the type signature before I write the function implementing it. It is a nice thing to look at as you are writing the function; it keeps you focused on what to do. Oh, and it is great documentation, that the compiler ensures is up to date -- if your function does something the type signature disagrees with, your program won't compile. Excellent!

One other thing I do, which is maybe not as common, is liberally using "type" aliases. Instead of:

   connectToDatabase :: String -> String -> String -> IO DatabaseHandle
I would write:

   type Dsn = String
   type Username = String
   type Password = String

   connectToDatabase :: Dsn -> Username -> Password -> IO DatabaseHandle
Self-documenting. (The type declarations also let you hang haddock documentation off them; good if you are using that type in more than one function. Then instead of documenting what a "Password" is everywhere you have a Password, you can just document it at the type declaration. Time-saving!)

As an aside, I could also get type safety by using "newtype" instead of "type". For something this simple, though, I find the flexibility of being able to use any old String to be rather useful; ``connectToDatabase (mkDsn "MySQL:foobar") (mkUsername "jrockway") (mkPassword "OH HAI")'' is a bit verbose, after all.

(I am also usually too lazy to derive Monad, MonadError, MonadWrite, and MonadIO instances for things like "type MyAction = ReaderT String (WriterT [String] (ErrorT String IO))", so I use type instead of newtype.)


> Personally, I write the type signature before I write the function implementing it. It is a nice thing to look at as you are writing the function; it keeps you focused on what to do. Oh, and it is great documentation, that the compiler ensures is up to date -- if your function does something the type signature disagrees with, your program won't compile. Excellent!

That partitions the function definition. And it's optional. And it could be somewhere else.

And you get to argue with folks who like point-free because it's shorter.


I load the module into ghci and use the :t command. But I mostly do this during development. This is the big reason that production-quality code should generally have explicit type signatures for top-level functions.


If we're talking about making code easy to read, type signatures are one of the best tools. Many times you can look at the name of a function and its arguments and you have a very good idea of what it does.

This is unlike many dynamic languages where the returned value could be an object of just about any class. Are we getting a hash? An array? A custom class? Who knows without reading the details of the function.


Well this kind of proves my point. If you have to compile code simply to read it, then the language is not very easy to read. What if you are reading the code in order to debug it and the code cant compile?

And I know code should have type signatures but it often doesn't.


And I know code should have type signatures but it often doesn't.

Often? All the libraries in GHC have type signatures. All the libraries on HackageDB I've used have type signatures. All the apps I've written have type signatures. Even most Haskell blog posts (and wiki pages) use type signatures.

I think you should just peruse some production Haskell code, and try writing a bit of your own. If you set aside your belief that everyone is out to confuse you for a few minutes, I think you'll find that most Haskell is quite readable, and quite writable. You'll see how other people compose functions, what the idioms are, and then stuff like "sum = foldr1 (+)" won't confuse you anymore. Instead, I think you'll see how you can read and write code more quickly, and how to understand your program in terms of function application instead of in terms of passing around boxes that hold data.


I think you will get over this after about 3 days of using Haskell for real. It's an essential technique that greatly improves readability once you get used to it. Remember, functional programming is about composing functions (and effects), not about data flowing through the program. Ideally you will never see your data, but sometimes it's easier to mention it. (I thought the same thing; "I hate point-free". But then I started writing serious code, and realized that it's much easier to read when you don't mention the irrelevant.)

You have saved a total of 4 keystrokes. And made that line of code look really weird and hard to understand.

Anyway, your examples are faulty. Here's what they would look like in real life:

Point-ful:

   sum :: Num a => [a] -> a
   sum xs = foldr (+) 0 xs
Point-free:

   sum :: Num a => [a] -> a
   sum = foldr (+) 0
So there is no confusion; there is actually less confusion. Sum is just a fold over (+) with a seed of 0. That's all it is; the fact that there is this "xs" variable is irrelevant -- we know a fold operates on a list already, there is no need to say that again.

If the sum example confuses you, that's fine, that's not the best example of point-free style. Perhaps these are easier to understand:

Tack on ".xml" to every filename in a list:

    (++ ".xml") <$> files
instead of:

    (\x -> x ++ ".xml") <$> files
Extract the `snd` part of a value during a monadic computation:

   f >>= return . snd
Instead of

   f >>= \x -> return $ snd x
And so on. Basically, point-free style lets you omit the parts that are understood. In the second example, of course you're operating on the value that >>= binds. That's the point of >>=. No need to say that every time.

So yeah, I think point free style is one of these annoying features of Haskell, that seem to be put in so that users of Haskell can pat themselves on the back and feel like they are very smart. But in the end it does not add much to the language and makes it much less accessible.

Nope, sorry. Point-free notation often makes code simpler and easier to understand (and easier to compose, of course). If it doesn't make your particular code easier to read, then the solution is simple -- don't use it in that case. But it is yet-another useful tool in the toolbox.


Nice examples, I was wondering about this myself (having not done any functional programming).

Any suggestions for starting a functional language? And which to start with? I'd love to eventually get really deep into it, but I'm really busy lately and can't devote a lot of time to get my brain over a high hurdle.


Speaking only for myself, I have to say I actually find your point-free version of "sum" more readable. The extra argument is just visual clutter.

Pointless style only gets really confusing when you end up with a bunch of composition operator sections shuffling the order of things around in unclear ways.


I like point-free when I want to express that I'm creating a function on a function. For example, memoize is a combinator that takes a function and returns another one. So I don't mind seeing (memoize f) as opposed to the sloppier (\x -> (memoize $ f x)).

Yet I don't enjoy point-free always. I'd probably be more comfortable with the pointed sum example (although I'd be able to understand either). When there are lots of . and $ floating around, point-free code can be tough to read for the uninitiated.

What you do get out of point-free is an appreciation for currying and partial function application, which are crucial if you want to understand the lambda-calculus on which functional programming is based. If you're doing calculations in lambda calculus (in which there's no distinction between first-class "data" values and functions, at least in the untyped version, because all objects are apply-able) you're constantly removing extraneous variables.


From the comments, an alternate form of the (.) . (.) operator:

  foo = fmap fmap fmap
Nothing says "welcome to Haskell!" like an inscrutable pile of fmaps. Oh, pointless style, how beautiful you are.

In all seriousness, though, there are a lot of cases where a pointless--er, "point-free"--style is actually clearer and more comprehensible, typically when you're writing functions intended to be used as combinators. Similarly, applicative style sometimes looks a lot cleaner than using do notation.


I am not convinced

(.^) = (.) . (.)

is supposed to be more readable than

(.^) f g x y = f (g x y)

?

I don't know Haskell, but even so. I think there might be exercises with a better benefit.


The former is definitely more suggestive.


Programming in balance with Yin and Yan :-)


One might say:

You used a drill to get a nail into the wall. Neither the drill, the nail nor you liked it. You concluded that the drill is generally bad. I concluded that I cannot use a drill if I need a hammer. :)


How do you mean? I just quoted from that web site... So you are saying their example for Point Free Programming was bad?


Yes, that was my point. That example of point free programming is just bad and confusing, as you pointed out.

On the other hand, if I try to keep my functions and methods small, I frequently have to write methods which just apply a function to all elements of a collection. In a point free style, I can just write

  manyFoo = map foo
And that's it. No loop, no recursion, no boundary issues, no clutter, just the statement 'this applies foo to everything in a list'.

Similar things work for other nicely curry-able problem statements. Imagine you have a higher-order-function traverse which takes two functions, and uses them to transform a binary tree to a value (applying the first higher order function in order to combine the left and right results of a tree and and the other function to turn leaves into values)

Now you can define:

   treesum = traverse + toNum
   preorder = traverse ++ toList
And so on.

Overall, the best advice I had about point-free style was: "Try to refactor your code to the form "f x = g x" and remove the x, if the refactoring can be done in a painless way". If you apply this, the drill drills well :)


I often use this algorithm:

1. write a function in pointed style 2. run it through pointfree 3. is it shorter/easier to read? then use pointfree's definition

This is really a personal shortcoming though because after 20+ years of writing code in "standard" languages my brain is sufficiently warped that I'm not always able to think of the short and clear solution without "grinding the gears".




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: