Let's revisit the functions in the example from yesterday.
readName :: IO String
readName = do
putStrLn "What is your name?"
getLine
The readName function has to return an action that will produce a string. The definition starts with the do keyword. The do keyword allows us to write IO code that looks very similar to imperative programs. We will see how to write IO code without do notation. getLine in the last line of the definition will return with a value of type IO String. That is the same as the readName function's return value. When using do notation the last line's value is going to be returned from the function.
- What is the type of putStrLn "What is your name?
- Delete the last line of readName and try to compile the code.
- The return function let's you construct IO actions that produce the value that is passed to it as a parameter. The type of return True is IO Boolean. It will produce the value True when evaluated. Try to fix the readName function using return.
main :: IO ()
main = do
name <- readName
putStrLn ("It is nice to meet you " ++ name ++ "!")
The type of main is IO (). It has the same type as the last line in the do block (putStrLn ...). The second line is more interesting. Inside a do block you can extract the value of other IO actions. The type of readName is IO String and the type of name is String. The left arrow notation can be used only to extract values from actions. If you would like to define values that are constructed by plain functions without any IO action, you can use let.
import Data.Char
main :: IO ()
main = do
name <- readName
let capitalizedName = capitalize name
putStrLn ("It is nice to meet you " ++ capitalizedName ++ "!")
where capitalize :: String -> String
capitalize = map toUpper
capitalizedName is a simple String just as capitalize name.
- The readMaybe function can be used to parse values of types that implement the Read typeclass. It returns with Maybe. If it fails to parse the value it returns Nothing. Try out the following lines in ghci.
import Text.Read
:t readMaybe
readMaybe :: Read a => String -> Maybe a
readMaybe "10" :: Maybe Int
readMaybe "10" :: Maybe Double
readMaybe "foo" :: Maybe Int
- Write a function that reads a string from the user and inside the function it converts it to an Int using readMaybe. (hint: The type should reflect the possibility of failure. Don't forget to import the Text.Read module.)
- Modify the function to keep asking for a number until the user enters a valid input. The type signature should be IO Int. (hint: Case expressions are allowed in do notation. Use recursion. You can construct IO actions from arbitrary values using return.)
- Is it possible to write a function that prints to the screen or reads some input whose return type does not contain IO?
- Is it possible to call a function whose return type does not contain IO, but deep in the stack, it calls a function with a return type containing IO?