# Parsing n lines to count vowels - HackerEarth

I have written a haskell program for the following 'Code Monk' Challenge at HackerEarth. Here is the challenge description. Basically, we are looking for the number of vowels in a string. The first input n is the number of string to parse, followed by n number of random strings.

And here is my implementation:

import Data.Char
main = do
numberOfTestCases <- getLine

processLine 0 = return()
processLine n = do
line <- getLine
print $countVowels line processLine (n-1) countVowels :: String -> Int countVowels line = length$ filter (elem ['a','e','i','o','u']) $map toLower line  My code is working correctly but as I'm fairly new to functional programming and Haskell I would like some comments about my code. More specifically, I would like the processLine function to not have a side effect, but I don't know how can I do that. Any other tips or comments would be greatly appreciated! ## 1 Answer # Type signatures You should add type signatures to all top-level bindings. processLine's returned value and main should have the same type, which can be checked with the type checker, if you add type signatures. The following code would compile, since main will have the type IO Int: processLine 0 = return 0  But that's not what you've intended. Therefore, make sure to add type-signatures to all your top-level bindings: main :: IO () main = … processLine :: Int -> IO () processLine n = …  By the way, processLine's type signature makes n's type unambigous, and therefore you don't need an explicit type in (read numberOfTestCases :: Int) anymore. # Use syntactic sugar ['a','e','i','o','u'] is "aeiou", which is easier to change and to read (in my opionion). That way, we get: countVowels :: String -> Int countVowels line = length$ filter (elem "aeiou") $map toLower line  By the way, if you use "aeiouAEIOU", you don't need to import Data.Char. # Keeping things pure(?) More specifically, I would like the processLine function to not have a side effect, but I don't know how can I do that. At some point, you need to read the line. You've chosen processLine to do so. Note that it is not feasible to remove IO from processLine, because you would end up with processLine = countVowels. Still, we can have a look at processLine to see what we can do. We can read the code literally: 1. If n is 0, do nothing 2. If n is greater than zero, do 1. read a line 2. count the vowels of the line 3. print the number of vowels 4. call processLine with n decreased by one Or we can interpret it as 1. repeat the following action n times: 1. read a line 2. count the vowels of the line 3. print the number of vowels 2. return nothing If we have a look at the modules in base, we'll notice that Control.Monad contains replicateM_, which does exactly the "repeat-n-times"-part: processAllLines :: Int -> IO () processAllLines n = replicateM_ n processSingleLine processSingleLine :: IO () processSingleLine = do line <- getLine print$ countVowels line


Note that processSingleLine can get shortened into getLine >>= print . countVowels, but I'm not sure whether you're ready for that yet.

# Keeping things actually pure

Alright, we've seen how one could use standard functions to process all those lines. But it's still in IO. Can't we do anything?

Well, what would the alternative be? We still need to process a bunch of lines. A "bunch of something" can be easily expressed with a list, so let us write the type signature of our potential candidate.

processList :: [String] -> [Int]


The implementation comes rather easy:

processList = map countVowels


Now we just need to get a list of strings. Since we now want to get something from the user, we have to use IO:

getUserLines :: Int -> IO [String]


Seems familiar, doesn't it? "repeat-an-action-n-times". This time, it's getLine. Since we don't want to throw the result away, we use replicateM (note the missing _):

getUserLines n = replicateM n getLine


We're now missing two last ingredients. First, we need to print a list of Int so that the challenged site accepts it:

printSolutions :: [Int] -> IO ()


Exercise: write printSolution. Note that there is a function that might help you in the process.

We end up with the following main:

main :: IO ()
main = do
userLines         <- getUserLines numberOfTestCases
printSolutions (processList userLines)


# Further remarks

Search some of the base modules whether they provide the functions you're looking for. And have a look at Prelude. You've used read after getLine, which is provided as a single function readLn (see above).

For completion, here's how I would (probably) solve the challenge:

import Control.Monad (replicateM_)

main :: IO ()
main = do
replicateM_ numberOfTest $getLine >>= print . countVowels countVowels :: String -> Int countVowels xs = length$ filter (elem "aeuioAEUIO") xs