Actions dependent on results of earlier actions
Bind: letting an action use an earlier result
(>>=) :: IO a -> (a -> IO b) -> IO b
Shouting back
Transforms the result (but does not print it back):
shout :: IO String
shout = liftM (map toUpper) getLine
shoutBack :: IO ()
shoutBack = shout >>= putStrLn
(>>=) :: IO a -> (a -> IO b) -> IO b
shout :: IO String
putStrLn :: String -> IO ()
shout >>= putStrLn :: IO ()
Shouting back twice
shoutBackTwice :: IO ()
shoutBackTwice =
shout >>= \ x -> putStrLn x >> putStrLn x
In GHCi
GHCi> shoutBack
Hello
Hello
GHCi> shoutBackTwice
can you hear me?
CAN YOU HEAR ME?
CAN YOU HEAR ME?
Optioning out of doing IO
return :: a -> IO a
An plan that when executed, perform no effects and returns the given result.
- Intuitively,
IO
a says that we may use effects to obtain ana
. We are not required to. - On the other hand,
a
says that we must not use effects to obtain ana
.
No escape from IO
There is no* function
runIO :: IO a -> a
If a value requires effects to obtain, we should not ever pretend that it does not.
- : There actually is one, called unsafePerformIO , but its use is generally not justified.
Escaping temporarily
(>>=) :: IO a -> (a -> IO b) -> IO b
- Gives us access to the a that results from the first action.
- But wraps it all up in another IO action.
Bind is the most general sequencing function
(>>) :: IO a -> IO b -> IO b
a1 >> a2 = a1 >>= \_ -> a2
Or:
(>>) :: IO a -> IO b -> IO b
ioa >> iob = ioa >>= const iob
const :: a -> b -> a
const a b = a
Bind and return can implement lifting
liftM :: (a -> b) -> IO a -> IO b
liftM f ioa = ioa >>= \ a -> return (f a)
liftM2 :: (a -> b -> c) -> IO a -> IO b -> IO c
liftM2 f ioa iob =
ioa >>= \ a -> iob >>= \ b -> return (f a b)
do notation
liftM2 :: (a -> b -> c) -> IO a -> IO b -> IO c
liftM2 f ioa iob =
ioa >>= \ a ->
iob >>= \ b ->
return (f a b)
liftM2 f ioa iob = do
a <- ioa
b <- iob
return (f a b)
A larger example
greeting :: IO ()
greeting =
putStrLn "What is your name?" >>
getLine >>= \ name ->
putStrLn "Where do you live?" >>
getLine >>= \ loc ->
let
answer
| loc == "Regensburg" = "Fantastic!"
| otherwise = "Sorry, don't know that."
in
putStrLn answer
greeting :: IO ()
greeting = do
putStrLn "What is your name?"
name <- getLine
putStrLn "Where do you live?"
loc <- getLine
let
answer
| loc == "Regensburg" = "Fantastic!"
| otherwise = "Sorry, don't know that."
putStrLn answer
More about do
A corner case is a single IO action:
helloWorld = do
putStrLn "Hello world"
is the same as writing
helloWorld =
putStrLn "Hello world"
Remember that a do is never required. It is just “syntactic sugar” for a chain of (>>=)
and (>>)
applications.
Nested do
loop :: IO ()
loop = do
putStrLn "Type 'q' to quit."
c <- getChar -- reads a single character
if c == 'q'
then putStrLn "Goodbye"
else do
putStrLn "Here we go again ..."
loop
If we want to embed a sequence commands into a subexpression and use do -notation for that, we need another do .
Note furthermore that there are lots of do blocks without return
.
About return
- The purpose of
return
in Haskell is to embed computations into the IO type. - As such,
return
can be used in many different places. - It is fine to use
return
in the middle of a sequence of commands. It does not jump anywhere.
This is fine:
do
n <- return 2
print n