Skip to content

Latest commit

 

History

History
111 lines (85 loc) · 3.13 KB

23.md

File metadata and controls

111 lines (85 loc) · 3.13 KB

Input / Output

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.

Exercise:

  • 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)