8
\$\begingroup\$

I had come up with the following code:

hello_worlds 0 = putStr ""
hello_worlds x = do putStrLn "Hello World"
                    hello_worlds (x-1)
main = do
   n <- readLn :: IO Int
   hello_worlds n

I am aware that it is making one extra call to putStr.

Is there any better way of doing this? If so, please give some advice regarding why I shouldn't go this way.

\$\endgroup\$

4 Answers 4

12
\$\begingroup\$

Instead of using explicit recursion, you can build your solution out of standard Prelude functions. This frequently ends up being more readable when the functions you're writing are more complex as common patterns are expressed by familiar names instead of properties of your recursive step.

hello_worlds n = putStrLn $ unlines (replicate n "Hello World")

In this case we have n repetitions of the string "Hello World", joined by newlines, and printed to stdout.

All top-level function definitions should include type annotations as well. It's a good source of documentation, it serves as a good check that you and the type checker agree on what your function is supposed to be doing, and it often gives the type checker enough information that you don't need to use type annotations inside the body of a function. In this case, by giving a type to hello_worlds you don't need to say what the return value of readLn is, because it can be seen that the n in main must be an Int since it's the argument to hello_worlds.

hello_worlds :: Int -> IO ()
hello_worlds n = putStrLn $ unlines (replicate n "Hello World")

main :: IO ()
main = do
    n <- readLn
    hello_worlds n

The last small change I'd make is to rename hello_worlds to helloWorlds. Haskell style prefers CamelCase.

\$\endgroup\$
1
  • \$\begingroup\$ Small nit: Could you replace repeat with replicate in the second full example? I think it is a typo. \$\endgroup\$ Commented Jan 11, 2021 at 8:43
4
\$\begingroup\$

Instead of printing empty string one can use return ()

hello_worlds 0 = return ()
hello_worlds x = do putStrLn "Hello World"
                    hello_worlds (x-1)
main = do
   n <- readLn :: IO Int
   hello_worlds n
\$\endgroup\$
4
\$\begingroup\$

There is a function for that called replicateM_. Here's how you use it.

import Control.Monad

main = do
    replicateM_ 20 $ putStrLn "Hello, world!"
\$\endgroup\$
2
\$\begingroup\$

In Haskell we like to abstract things! Something like doing n times the same IO action can be useful:

redoAction :: Int -> IO () -> IO ()
redoAction n action
    | n <= 0    = return () -- this is the base case (we also check that `n` is correct!)
    | otherwise = do action  
                     redoAction (n-1) action

With this new function, you can create your hello_worlds:

hello_worlds :: Int -> IO ()
hello_worlds n = redoAction n (putStrLn "Hello World")

and your main function doesn't change! This is a successful refactorisation that lead to reusable code without changing anything where code doesn't need to be changed (here: in the main function).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.