How can I use pure functions inside IO functions? :-/
For example: I'm reading a file (IO function) and I want to parse its context, a string, by using a pure function with referential transparency.
It seems such worlds, pure functions and IO functions, are separated. How can I possibly bridge them?
Haskell is a pure language Being pure means that the result of any function call is fully determined by its arguments. Procedural entities like rand() or getchar() in C, which return different results on each call, are simply impossible to write in Haskell.
Haskell is a functional programming language, in which (almost) all expressions are pure; thus, Haskell is a purely functional programming language. Excellent.
I/O is inherently impure: input operations undermine referential transparency, and output operations create side effects.
A function has no side effects. Calling a function once is the same as calling it twice and discarding the result of the first call. In fact if you discard the result of any function call, Haskell will spare itself the trouble and will never call the function.
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
, ordo
notationIt's really up to you which one you prefer. I personally recommend fmap
.
You can also consider liftM function from Control.Monad.
A little example to help you (run it into ghci as you are under the IO Monad)
$ import Control.Monad -- to emerge liftM
$ import Data.Char -- to emerge toUpper
$ :t map to Upper -- A pure function
map toUpper :: [Char] -> [Char]
$ :t liftM
liftM :: Monad m => (a1 -> r) -> m a1 -> m r
$ liftM (map toUpper) getLine
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With