Learn You a Haskell for Great Good! - Miran Lipovaca [99]
instance Functor ((->) r) where
fmap f g = (\x -> f (g x))
First, let’s think about fmap’s type:
fmap :: (a -> b) -> f a -> f b
Next, let’s mentally replace each f, which is the role that our functor instance plays, with (->) r. This will let us see how fmap should behave for this particular instance. Here’s the result:
fmap :: (a -> b) -> ((->) r a) -> ((->) r b)
Now we can write the (->) r a and (->) r b types as infix r -> a and r -> b, as we normally do with functions:
fmap :: (a -> b) -> (r -> a) -> (r -> b)
Okay, mapping a function over a function must produce a function, just like mapping a function over a Maybe must produce a Maybe, and mapping a function over a list must produce a list. What does the preceding type tell us? We see that it takes a function from a to b and a function from r to a and returns a function from r to b. Does this remind you of anything? Yes, function composition! We pipe the output of r -> a into the input of a -> b to get a function r -> b, which is exactly what function composition is all about. Here’s another way to write this instance:
instance Functor ((->) r) where
fmap = (.)
This makes it clear that using fmap over functions is just function composition. In a script, import Control.Monad.Instances, since that’s where the instance is defined, and then load the script and try playing with mapping over functions:
ghci> :t fmap (*3) (+100)
fmap (*3) (+100) :: (Num a) => a -> a
ghci> fmap (*3) (+100) 1
303
ghci> (*3) `fmap` (+100) $ 1
303
ghci> (*3) . (+100) $ 1
303
ghci> fmap (show . (*3)) (+100) 1
"303"
We can call fmap as an infix function so that the resemblance to . is clear. In the second input line, we’re mapping (*3) over (+100), which results in a function that will take an input, apply (+100) to that, and then apply (*3) to that result. We then apply that function to 1.
Just like all functors, functions can be thought of as values with contexts. When we have a function like (+3), we can view the value as the eventual result of the function, and the context is that we need to apply the function to something to get to the result. Using fmap (*3) on (+100) will create another function that acts like (+100), but before producing a result, (*3) will be applied to that result.
The fact that fmap is function composition when used on functions isn’t so terribly useful right now, but at least it’s very interesting. It also bends our minds a bit and lets us see how things that act more like computations than boxes (IO and (->) r) can be functors. The function being mapped over a computation results in the same sort of computation, but the result of that computation is modified with the function.
Before we go on to the rules that fmap should follow, let’s think about the type of fmap once more:
fmap :: (Functor f) => (a -> b) -> f a -> f b
The introduction of curried functions in Chapter 5 began by stating that all Haskell functions actually take one parameter. A function a -> b -> c takes just one parameter of type a and returns a function b -> c, which takes one parameter and returns c. That’s why calling a function with too few parameters (partially applying it) gives us back a function that takes the number of parameters that we left out (if we’re thinking about functions as taking several parameters again). So a -> b -> c can be written as a -> (b -> c), to make the currying more apparent.
In the same vein, if we write fmap :: (a -> b) -> (f a -> f b), we can think of fmap not as a function that takes one function and a functor value and returns a functor value, but as a function that takes a function and returns a new function that’s just like the old one, except that it takes a functor value as a parameter and returns a functor value as the result. It takes an a -> b function and returns a function f a -> f b. This is called lifting a function. Let’s play around with that idea using GHCi’s :t command:
ghci> :t fmap (*2)
fmap (*2) :: (Num a, Functor f) => f a -> f a
ghci>