Francesco,
thanks, that was very enlightening. That concatenated functions should have matching inputs / outputs is obvious of course, but I just didn't think of that. Duh!
That IO () can't be converted to String is probably just as obvious, but it wasn't for me.
For hello_pure I tried this:
hello_pure :: Int -> String
hello_pure n
| n < 1 = ""
| otherwise = "Hello World" ++ "\n" ++ hello_pure ( n - 1 )
And it works, although
++ "\n" ++
doesn't feel so elegant.