Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell - How can I use pure functions inside IO functions?

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?

like image 313
Juan Carlos Kuri Pinto Avatar asked Feb 17 '13 00:02

Juan Carlos Kuri Pinto


People also ask

How is Haskell IO pure?

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.

Are all functions in Haskell pure?

Haskell is a functional programming language, in which (almost) all expressions are pure; thus, Haskell is a purely functional programming language. Excellent.

Is IO A impure?

I/O is inherently impure: input operations undermine referential transparency, and output operations create side effects.

Why does a pure function have no side effects Haskell?

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.


2 Answers

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 as to bs, 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.

like image 145
Gabriella Gonzalez Avatar answered Sep 24 '22 21:09

Gabriella Gonzalez


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 
like image 31
zurgl Avatar answered Sep 21 '22 21:09

zurgl