Haskell really hates side-effects, and puts a large amount of effort into controlling them.
One of the interesting ways Haskell fights side-effects is with types. It pushes all side-effects up into the type-system. For example, imagine you've got a getPerson function. In Haskell it might look like:
getPerson :: UUID -> Database Person
You can read that as, "takes a UUID and returns a Person in the context of a Database". This is interesting - you can look at the type signature of a Haskell function and know for certain which side-effects are involved. And which aren't. You can also make guarantees like, "this function won't access the filesystem, because it's not declared that kind of side-effect."
main :: IO () main = do print "Enter how many 'Smile!' lines you want: " numberOfSmiles <- readLn mapM_ print $ smile numberOfSmiles smile :: Int -> [[Char]] smile n = map concat $ zipWith (replicate) [n, n - 1..1] $ cycle["Smile!"]
imperative style: import Control.Monad (forM_) test :: Int -> IO () test n = forM_ [0..n] (\i -> putStrLn $ show $ i*i)