Input/Output (continued)
IO
is a Monad
.
The type definition lives in Prelude
/ GHC.Types
...
newtype IO a = IO ( ... RealWorld -> ( ... RealWorld, a ... ))
... and the instance definition lives in GHC.Base
:
instance Monad IO where
...
(>>=) = bindIO
bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = ... unIO ..
unIO :: IO a -> (... RealWorld -> ( ... RealWorld, a ...))
unIO (IO a) = a
In Control.Monad.ST
:
data RealWorld :: *
"RealWorld is deeply magical. It is primitive... We never manipulate values of type RealWorld; it's only used in the type system..."
So, we can't implement this instance ourselves. (After all,
it performs side-effects, for which we have no mechanisms.)
But the built-in instance satisfies the Monad
interface and laws.
Iteration via Monad
helpers
Loop until empty line.
getLinesUntilEmpty :: IO ()
getLinesUntilEmpty = do
putStrLn "Something to say?"
s <- getLine
if s /= ""
then getLinesUntilEmpty
else putStrLn "Goodbye."
Factor out common pattern:
-- mDoWhile_ :: (a -> Bool) -> IO a -> IO ()
mDoWhile_ :: (Monad m) => (a -> Bool) -> m a -> m ()
mDoWhile_ f action = do
x <- action
if f x
then mDoWhile_ f action
else pure ()
getLinesUntilEmpty_ :: IO ()
getLinesUntilEmpty_ =
mDoWhile_ (/= "") (putStrLn "More?" >> getLine)
Now let's loop until empty line, and then reverse each line and print them in reverse order. Let's start with:
mDoWhile :: (Monad m) => (a -> Bool) -> m a -> m [a]
mDoWhile f action = do
x <- action
if f x then do
xs <- mDoWhile f action
pure (x : xs)
else
pure []
mDoWhile_ :: (Monad m) => (a -> Bool) -> m a -> m ()
mDoWhile_ f action = mDoWhile f action >> pure ()
repeatBackwards :: IO ()
repeatBackwards =
let getLinesUntilEmpty = mDoWhile (/= "") getLine in
-- do {lines <- getLinesUntilEmpty; putStrLn $ unlines $ reverse $ map reverse lines}
-- getLinesUntilEmpty >>= \lines -> putStrLn $ unlines $ reverse $ map reverse lines
-- getLinesUntilEmpty >>= putStrLn . unlines . reverse . map reverse
putStrLn . unlines . reverse . map reverse =<< getLinesUntilEmpty
The last step uses (=<<)
(i.e. reverse bind) to keep
the "pipeline" flowing in one direction.
Alternatively, could uses reverse (i.e. left-to-right) composition to write the pipeline left-to-right:
(>>>) = flip (.)
repeatBackwards =
getLinesUntilEmpty >>= map reverse >>> reverse >>> unlines >>> putStrLn
Note: The (>>>)
operator is defined in Control.Category
, but
with a different precedence level which requires writing more
parentheses in the definition above.