Today we will learn how to write IO code without do notation. Let's revisit the functions in the example from yesterday.
readName :: IO String
readName = do
putStrLn "What is your name?"
getLine
do notation combines the IO actions inside its body to a single IO action. It hides two operators. The simpler one makes sure that the two operations it combines will be executed in order. That is getLine will be executed after putStrLn in the code above. This operator is >>. Let's desugar the code above:
readName :: IO String
readName = putStrLn "What is your name?" >> getLine
The other operator that can be used is >>=. It is called the bind operator. Bind operator is more complicated. It let's you use the result of the first action in the second action. If you have a look at the previous example, you will see that we did not use the result of the first action. The result was (). Let's see an example of the bind operator.
import Text.Read
readAge :: IO (Maybe Int)
readAge = do
putStrLn "What's your age?"
ageString <- getLine
return (readMaybe ageString)
After desugaring it:
import Text.Read
parseInt :: String -> IO (Maybe Int)
parseInt str = return (readMaybe str)
readAge :: IO (Maybe Int)
readAge = putStrLn "What's your age?" >> getLine >>= parseInt
The important part here is that we combine the getLine and parseInt functions with the bind operator. This means that the result of the first action can be used in the second action. bind operator has the same ordering properties as the >> operator.
It is important to have a look at the type of the expressions. The left hand side of both operators is a simple IO action. However the right hand side of the bind operator is a bit more complicated. It is a function whose only parameter has the same type as the inner type of the IO action on its left.
(>>=) :: IO a -> (a -> IO b) -> IO b
IO a is the type of the left operand, it stands for getLine in this case. a-> IO b is the right operand, it stands for parseInt and the resulting type is the type of readAge.
- Rewrite the following functions without using do notation.
import Text.Read
readAge :: IO Int
readAge = do
putStrLn "What's your age?"
ageStr <- getLine
case readMaybe ageStr of
Just age -> return age
Nothing -> readAge
- Rewrite the following function without using >> and >>= using do notation.
import Text.Read
parseInt :: String -> IO (Maybe Int)
parseInt str = return (readMaybe str)
checkInput :: Int -> Int -> IO ()
checkInput num guessed
| num < guessed = putStrLn "less" >> guess num
| guessed < num = putStrLn "more" >> guess num
| otherwise = putStrLn "You won!"
ensureValidInput :: Maybe Int -> IO Int
ensureValidInput input = case input of
Just x -> return x
Nothing -> readGuess
readGuess :: IO Int
readGuess = putStrLn "Pick a number! " >> getLine >>= parseInt >>= ensureValidInput
guess :: Int -> IO ()
guess num = readGuess >>= (checkInput num)