Point-Free style: What is it good for?
buffered.io
buffered.io
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)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.
"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.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.
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.
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.)
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.
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.
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.
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.
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.
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.
(.^) = (.) . (.)
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.
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. :)
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 :)
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".