简体   繁体   中英

How to write bind functions in point-free style?

I often find myself having to explicitly specify return to wrap an expression in IO/Maybe/Either , etc... but I'm sure there must be a better way to make it point-free.

For example:

countLines :: FilePath -> IO Int countLines file = readFile file >>= countLines where countLines = (\\d -> return $ (length . lines) d)

Is there a way to achieve a point-free style for the countLines function? Writing lambdas for each step of the >>= is tiresome.

countLines :: FilePath -> IO Int
countLines file = readFile file >>= countLines
     where countLines = (\d -> return $ (length . lines) d)

By the way, it is not a good practice to use the same name of the function in a where clause: the function looks recursive even if it is not.

Back to the question, the code above means

countLines file = readFile file >>= (\d -> return $ (length . lines) d)

which means

countLines file = readFile file >>= (return . (length . lines))

which can be written as

countLines file = length . lines <$> readFile file
-- or
countLines file = fmap (length . lines) $ readFile file

One can go further and also remove the file point, but readability would be slightly harmed.

countLines = fmap (length . lines) . readFile

Is there a way to achieve a point-free style for the countLines function? Writing lambdas for each step of the >>= is tiresome.

Well, pointfree style is hardly more readable in the general case. Why don't you use do notation instead?

countLines :: FilePath -> IO Int
countLines file = do
   d <- readFile file
   return . length . lines $ d

This is not as elegant as the pointfree-ish variant in this case, but is much more general. Even when chaining many >>= s sometimes the following style is used:

foo =
  m1 >>= \x1 ->
  m2 >>= \x2 ->
  m3 >>= \x3 ->
  ...
  mN >>= \xN ->
  return (...)

To make your function here

countLines :: FilePath -> IO Int
countLines file = readFile file >>= countLines
 where countLines = (\d -> return $ (length . lines) d)

pointfree, we need to first refactor it to this:

countLines :: FilePath -> IO Int
countLines file = readFile file >>= countLines
 where countLines = (\d -> return $ (length . lines) $ d)

Now, we can compose the return with the other functions, like so (we can remove the parenthesis):

countLines :: FilePath -> IO Int
countLines file = readFile file >>= countLines
 where countLines = (\d -> return . length . lines $ d)

Now we can take out the point (again, remove parenthesis):

countLines :: FilePath -> IO Int
countLines file = readFile file >>= countLines
 where countLines = return . length . lines

Now the function can be inlined:

countLines :: FilePath -> IO Int
countLines file = readFile file >>= return . length . lines

As an added bonus, if you want to make countLines totally pointfree, you can use the Kleisi composition operator (found in Control.Monad ) instead of bind, like so:

countLines :: FilePath -> IO Int
countLines = readFile >=> return . length . lines

although it is arguable that the other version is more readable.

The last one can also be written like:

countLines :: FilePath -> IO Int
countLines = return . length . lines <=< readLine

which has the advantage that you can read the function from right to left and see exactly how the function "flows".

Your code

countLines file = readFile file >>= countLines
  where countLines = (\d -> return $ (length . lines) d)

contains the anonymous function

\d -> return $ (length . lines) d

which is equivalent to

\d -> return ((length . lines) d)

and the point d can be removed using function composition to give

return . (length . lines)

This allows you to write

countLines :: FilePath -> IO Int
countLines file = readFile file >>= return . (length . lines)

The pattern a >>= return . f a >>= return . f is equivalent to fmap fa , so you can also write

countLines :: FilePath -> IO Int
countLines file = fmap (length . lines) (readFile file)

and you can now remove the point file using function composition as well,

countLines :: FilePath -> IO Int
countLines = fmap (length . lines) . readFile

Personally I find this more readable than the original, but opinions may differ.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM