Hennes
Hennes

Reputation: 1370

Reading numbers inline

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?

Upvotes: 1

Views: 339

Answers (3)

Zeta
Zeta

Reputation: 105876

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.

Upvotes: 3

jub0bs
jub0bs

Reputation: 66183

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

Upvotes: 2

Dogbert
Dogbert

Reputation: 222040

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"

Upvotes: 2

Related Questions