1

Imagine I read an input block via stdin that looks like this:

3
12
16
19

The first number is the number of following rows. I have to process these numbers via a function and report the results separated by a space.

So I wrote this main function:

main = do         
    num <- readLn
    putStrLn $ intercalate " " [ show $ myFunc $ read getLine | c <- [1..num]]

Of course that function doesn't compile because of the read getLine.

But what is the correct (read: the Haskell way) way to do this properly? Is it even possible to write this function as a one-liner?

Bakuriu
  • 98,325
  • 22
  • 197
  • 231
Hennes
  • 1,340
  • 1
  • 10
  • 26

3 Answers3

3

Is it even possible to write this function as a one-liner?

Well, it is, and it's kind of concise, but see for yourself:

main = interact $ unwords . map (show . myFunc . read) . drop 1 . lines

So, how does this work?

  1. interact :: (String -> String) -> IO () takes all contents from STDIN, passes it through the given function, and prints the output.
  2. We use unwords . map (show . myFunc . read) . drop 1 . lines :: String -> String:
    1. lines :: String -> [String] breaks a string at line ends.
    2. drop 1 removes the first line, as we don't actually need the number of lines.
    3. map (show . myFunc . read) converts each String to the correct type, uses myFunc, and then converts it back to a `String.
    4. unwords is basically the same as intercalate " ".

However, keep in mind that interact isn't very GHCi friendly.

Zeta
  • 103,620
  • 13
  • 194
  • 236
  • 1
    Nice! Why not replace `fmap` by `<$>`, though, in order to get rid of the outer parens? – jub0bs Aug 18 '15 at 12:17
  • 1
    @Jubobs: Because I'm still on GHC 7.8.x. And the default installed GHC in Ubuntu is ancient (7.6.3). This answer works with 7.4+ (maybe even earlier versions) without importing `Control.Applicative`, and I like to keep my answers somewhat backward compatible ;). – Zeta Aug 18 '15 at 12:19
  • Fair enough. For my part, since `<$>` is part of the `Prelude`, I use it all the time. – jub0bs Aug 18 '15 at 12:20
2

You can build a list of monadic actions with <$> (or fmap) and execute them all with sequence.

λ intercalate " " <$> sequence [show . (2*) . read <$> getLine | _ <- [1..4]]
1
2
3
4
"2 4 6 8"
Dogbert
  • 212,659
  • 41
  • 396
  • 397
  • Works nice in the ghci environment, but doesn't print anything compiled as a program: "putStrLn <$> intercalate " " <$> sequence [ show . myFunc . read <$> getLine | _ <- [1..num]]". It only works, when I bind this to a variable and print this variable in the next statement. – Hennes Aug 18 '15 at 11:28
2

Is it even possible to write this function as a one-liner?

Sure, but there is a problem with the last line of your main function. Because you're trying to apply intercalate " " to

[ show $ myFunc $ read getLine | c <- [1..num]]

I'm guessing you expect the latter to have type [String], but it is in fact not a well-typed expression. How can that be fixed? Let's first define

getOneInt :: IO Int
getOneInt = read <$> getLine

for convenience (we'll be using it multiple times in our code). Now, what you meant is probably something like

[ show . myFunc <$> getOneInt | c <- [1..num]]

which, if the type of myFunc aligns with the rest, has type [IO String]. You can then pass that to sequence in order to get a value of type IO [String] instead. Finally, you can "pass" that (using =<<) to

putStrLn . intercalate " "

in order to get the desired one-liner:

import Control.Monad ( replicateM )
import Data.List     ( intercalate )

main :: IO ()
main = do
    num  <- getOneInt
    putStrLn . intercalate " " =<< sequence [ show . myFunc <$> getOneInt | c <- [1..num]]
  where
    myFunc = (* 3) -- for example

getOneInt :: IO Int
getOneInt = read <$> getLine

In GHCi:

λ> main
3
45
23
1
135 69 3

Is the code idiomatic and readable, though? Not so much, in my opinion...

[...] what is the correct (read: the Haskell way) way to do this properly?

There is no "correct" way of doing it, but the following just feels more natural and readable to me:

import Control.Monad ( replicateM )
import Data.List     ( intercalate )

main :: IO ()
main = do
    n  <- getOneInt
    ns <- replicateM n getOneInt
    putStrLn $ intercalate " " $ map (show . myFunc) ns
  where
    myFunc = (* 3) -- replace by your own function

getOneInt :: IO Int
getOneInt = read <$> getLine

Alternatively, if you want to eschew the do notation:

main =
    getOneInt                                        >>=
    flip replicateM getOneInt                        >>=
    putStrLn . intercalate " " . map (show . myFunc)
  where
    myFunc = (* 3) -- replace by your own function
jub0bs
  • 60,866
  • 25
  • 183
  • 186
  • Not neccessarily the requested one-liner, but structured nicely. I think I'll take this to start with. Many thanks. – Hennes Aug 18 '15 at 11:31
  • So if I got you correctly it's not possible to read numbers from stdin without that extra function getOneInt as Dogbert stated in his comment? – Hennes Aug 18 '15 at 11:42
  • @Hennes You could just use `read <$> getLine :: IO Int` instead. However, since it's used in multiple places of the code, it makes sense to define a `getOneInt` function. – jub0bs Aug 18 '15 at 12:04