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?
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
.
Alex Horsman helped me. He said:
"Perhaps I'm misunderstanding, but that sounds pretty simple?
do {x <- ioFunc; return (pureFunc x)}"
And then I solved my problem:
import System.IO
import Data.List
getFirstPart line Nothing = line
getFirstPart line (Just index) = fst $ splitAt index line
eliminateComment line =
getFirstPart line $ elemIndex ';' line
eliminateCarriageReturn line =
getFirstPart line $ elemIndex '\r' line
eliminateEntersAndComments :: String -> String
eliminateEntersAndComments text =
concat $ map mapFunction $ lines text
where
mapFunction = (++ " ") . eliminateCarriageReturn . eliminateComment
main = do {
contents <- readFile "../DWR-operators.txt";
return (eliminateEntersAndComments contents)
}
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
The actual answer is as follows:
main = do
val <- return (purefunc ...arguments...)
...more..actions...
return
wraps it in the appropriate monad so that do
can assign it to val
.