Reputation: 1370
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
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?
interact :: (String -> String) -> IO ()
takes all contents from STDIN, passes it through the given function, and prints the output.unwords . map (show . myFunc . read) . drop 1 . lines :: String -> String
:
lines :: String -> [String]
breaks a string at line ends.drop 1
removes the first line, as we don't actually need the number of lines.map (show . myFunc . read)
converts each String
to the correct type, uses myFunc
, and then converts it back to a `String.unwords
is basically the same as intercalate " "
.However, keep in mind that interact
isn't very GHCi friendly.
Upvotes: 3
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