The simplest way is to use fmap
, which has the following type:
fmap :: (Functor f) => (a -> b) -> f a -> f b
IO
implements Functor
, which means that we can specialize the above type by substituting IO
for f
to get:
fmap :: (a -> b) -> IO a -> IO b
In other words, we take some function that converts a
s to b
s, and use that to change the result of an IO
action. For example:
getLine :: IO String
>>> getLine
Test<Enter>
Test
>>> fmap (map toUpper) getLine
Test<Enter>
TEST
What just happened there? Well, map toUpper
has type:
map toUpper :: String -> String
It takes a String
as an argument, and returns a String
as a result. Specifically, it uppercases the entire string.
Now, let's look at the type of fmap (map toUpper)
:
fmap (map toUpper) :: IO String -> IO String
We've upgraded our function to work on IO
values. It transforms the result of an IO
action to return an upper-cased string.
We can also implement this using do
notation, to:
getUpperCase :: IO String
getUpperCase = do
str <- getLine
return (map toUpper str)
>>> getUpperCase
Test<Enter>
TEST
It turns out that every monad has the following property:
fmap f m = do
x <- m
return (f x)
In other words, if any type implements Monad
, then it should always be able to implement Functor
, too, using the above definition. In fact, we can always use the liftM
as the default implementation of fmap
:
liftM :: (Monad m) => (a -> b) -> m a -> m b
liftM f m = do
x <- m
return (f x)
liftM
is identical to fmap
, except specialized to monads, which are not as general as functors.
So if you want to transform the result of an IO
action, you can either use:
fmap
,
liftM
, or
do
notation
It's really up to you which one you prefer. I personally recommend fmap
.