Online Book Reader

Home Category

Learn You a Haskell for Great Good! - Miran Lipovaca [98]

By Root 430 0
how IO is an instance of Functor. When we fmap a function over an I/O action, we want to get back an I/O action that does the same thing but has our function applied over its result value. Here’s the code:

instance Functor IO where

fmap f action = do

result <- action

return (f result)

The result of mapping something over an I/O action will be an I/O action, so right off the bat, we use the do syntax to glue two actions and make a new one. In the implementation for fmap, we make a new I/O action that first performs the original I/O action and calls its result result. Then we do return (f result). Recall that return is a function that makes an I/O action that doesn’t do anything but only yields something as its result.

The action that a do block produces will always yield the result value of its last action. That’s why we use return to make an I/O action that doesn’t really do anything; it just yields f result as the result of the new I/O action. Check out this piece of code:

main = do line <- getLine

let line' = reverse line

putStrLn $ "You said " ++ line' ++ " backwards!"

putStrLn $ "Yes, you said " ++ line' ++ " backwards!"

The user is prompted for a line, which we give back, but reversed. Here’s how to rewrite this by using fmap:

main = do line <- fmap reverse getLine

putStrLn $ "You said " ++ line ++ " backwards!"

putStrLn $ "Yes, you really said " ++ line ++ " backwards!"

Just as we can fmap reverse over Just "blah" to get Just "halb", we can fmap reverse over getLine. getLine is an I/O action that has a type of IO String, and mapping reverse over it gives us an I/O action that will go out into the real world and get a line and then apply reverse to its result. In the same way that we can apply a function to something that’s inside a Maybe box, we can apply a function to what’s inside an IO box, but it must go out into the real world to get something. Then when we bind it to a name using <-. The name will reflect the result that already has reverse applied to it.

The I/O action fmap (++"!") getLine behaves just like getLine, except that its result always has "!" appended to it!

If fmap were limited to IO, its type would be fmap :: (a -> b) -> IO a -> IO b. fmap takes a function and an I/O action and returns a new I/O action that’s like the old one, except that the function is applied to its contained result.

If you ever find yourself binding the result of an I/O action to a name, only to apply a function to that and call that something else, consider using fmap. If you want to apply multiple functions to some data inside a functor, you can declare your own function at the top level, make a lambda function, or, ideally, use function composition:

import Data.Char

import Data.List

main = do line <- fmap (intersperse '-' . reverse . map toUpper) getLine

putStrLn line

Here’s what happens if we run this with the input hello there:

$ ./fmapping_io

hello there

E-R-E-H-T- -O-L-L-E-H

The intersperse '-' . reverse . map toUpper function takes a string, maps toUpper over it, applies reverse to that result, and then applies intersperse '-' to that result. It’s a prettier way of writing the following:

(\xs -> intersperse '-' (reverse (map toUpper xs)))

Functions As Functors


Another instance of Functor that we’ve been dealing with all along is (->) r. But wait! What the heck does (->) r mean? The function type r -> a can be rewritten as (->) r a, much like we can write 2 + 3 as (+) 2 3. When we look at it as (->) r a, we can see (->) in a slightly different light. It’s just a type constructor that takes two type parameters, like Either.

But remember that a type constructor must take exactly one type parameter so it can be made an instance of Functor. That’s why we can’t make (->) an instance of Functor; however, if we partially apply it to (->) r, it doesn’t pose any problems. If the syntax allowed for type constructors to be partially applied with sections (like we can partially apply + by doing (2+), which is the same as (+) 2), we could write (->) r as (r ->).

How are functions functors?

Return Main Page Previous Page Next Page

®Online Book Reader