Currently reading through this article (which is pretty brilliant btw) and have a pretty simple question:
If I combine two functions like (+3)
and (+2)
with <$>
, it seems to give me a new function that adds 5 to whatever is passed to it. If I do the same with the function composition operator, i.e. (+3) . (+2)
, would it not do the same thing? If that is true, is there a relationship here between these two operators such that they do the same thing in this simple case?
Is this even an intelligent question?
The functions fmap
and <$>
both have the same type:
> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
> :t (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b
While the function .
is
> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
So how is it possible that we can use fmap
on a function and end up with .
? I'm assuming you understand what a Functor is, so now you have to understand that "functions" are Functors. How so?
> :i (->)
data (->) a b -- Defined in `GHC.Prim'
instance Monad ((->) r) -- Defined in `GHC.Base'
instance Functor ((->) r) -- Defined in `GHC.Base'
instance Applicative ((->) a) -- Defined in `Control.Applicative'
Unlike Just
, []
and Left
, functions do not have a constructor that can be used. The Functor instance is applied to the syntax itself. We can see from :info in ghci that the syntactic arrow ->
actually has an instance for functor.
What happens when we look at the type of +3?
> :t (+3)
(+3) :: Num a => a -> a
So the function (+3) is a Functor that accepts an a and returns an a. When we use fmap
on a Functor and that also gives us back a Functor, we get nested Functors:
> :t fmap Just (Just 3)
fmap Just (Just 3) :: Num a => Maybe (Maybe a)
> :t fmap (replicate 5) [1,2,3]
fmap (replicate 5) [1,2,3] :: Num a => [[a]]
Likewise, when we apply fmap
to two functions we get a function inside a function. The only difference is that they are fused together:
> :t (fmap (+3) (+2))
(fmap (+3) (+2)) :: Num a => a -> a
Why doesn't this result in the type (->) (->) a a
? We have to remember that the first argument of fmap
is a function (a -> b)
and not necessarily a Functor. So when we do fmap g (Just 5)
we can have any transformation. But whenever we perform fmap
on a function we know that it will always result with a function inside of a function.
Thus fmap (+3) (+2)
evaluates to something like this: \x -> (\x' -> x' + 3) (x + 2)
. That is a really roundabout way of writing (+3) . (+2)
.
> :t (fmap (+3) (+2))
(fmap (+3) (+2)) :: Num a => a -> a
> :t ((.) (+3) (+2))
((.) (+3) (+2)) :: Num a => a -> a
Normally to get around the concat problem (Maybe (Maybe a))
or [[a]]
we actually need to rely on it being a Monad a
, so that we can use a bind >>=
. But functions (->)
are a special case because we know that every single time we use fmap
on a function, it will always give us a function in side of a function. This cannot be said for any other Functor except ->
. As such we can be sure to always concatenate fmap
on functions.
Therefore any f <$> g == f . g
Edit: A quick side note, if you do this fmap (+) (+0)
you end up with a function inside a function. In this case the monadic bind (>>=
) is actually needed to concatenate the functions:
> :t fmap (+) (+0)
fmap (+) (+0) :: Num a => a -> a -> a
> :t (+0) >>= (+)
(+0) >>= (+) :: Num b => b -> b
> let bindfunc = (+0) >>= (+)
> bindfunc 5
10
Which is not entirely unlike the behaviour we get when we do [1,2] >>= replicate 5
:
> [1,2] >>= replicate 5
[1,1,1,1,1,2,2,2,2,2]
To find information about the Functor
instance for functions, match up the types to find the relevant instance:
fmap :: (a -> b) -> f a -> f b
Then here a ~ Int
, b ~ Int
and f ~ (->) Int
.
You can see all of the Functor
instances that come with GHC here. (->)
is just an infix type operator with two type parameters. We usually see it applied as Int -> Int
, but this is equivalent to (->) Int Int
.
There is a Functor
instance for the (partially applied) type (->) r
(for any type r::*
).
Looking at the ((->) r)
instance for Functor
, we see that fmap = (.)
, so there is no practical difference between (+3) . (+2)
and fmap (+3) (+2)
(same as (+3) <$> (+2)
.
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