Below there's a horrible example. You have been warned.
Consider the pseudocode:
var x = 23
while (x != 0) {
print "x not yet 0, enter an adjustment"
x = x + read()
}
print "x reached 0! Exiting"
Here's its piece-by-piece translation in Haskell, using an imperative style as much as possible.
import Data.IORef
main :: IO ()
main = do
x <- newIORef (23 :: Int)
let loop = do
v <- readIORef x
if v == 0
then return ()
else do
putStrLn "x not yet 0, enter an adjustment"
a <- readLn
writeIORef x (v+a)
loop
loop
putStrLn "x reached 0! Exiting"
The above is indeed horrible Haskell. It simulates the while loop using the recursively-defined loop
, which is not too bad. But it uses IO everywhere, including for mimicking imperative-style mutable variables.
A better approach could be to remove those IORef
s.
main = do
let loop 0 = return ()
loop v = do
putStrLn "x not yet 0, enter an adjustment"
a <- readLn
loop (v+a)
loop 23
putStrLn "x reached 0! Exiting"
Not elegant code by any stretch, but at least the "while guard" now does not do unnecessary IO.
Usually, Haskell programmers strive hard to separate pure computation from IO as much as possible. This is because it often leads to better, simpler and less error-prone code.