Skip to content

Why Applicative Functor Currying While Fmap

Why applicative functor could currying a function while calling fmap? What are processes to calculate it?

ghci> (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5
[8.0,10.0,2.5]

This is my anwser for the question Applicative functors: why can fmap take a function with more than one argument? in stackoverflow.

For your question, especially "...takes three arguments, not just one...", it's the topic of applicative functor and it doesn't matter the functor. Hence, simply seeing the definition of fmap doesn't help to understand the confusion.

Before clarifying your confusion, please allow me to introduce the functor and applicative functor again.

Functions Are Functors

As the definition refers and everyone knows, a functor allows one to apply a function to values inside a generic type without changing the structure of the generic type. The implication is functions are also functors, which could be represented as (->) r according to the source code.

instance Functor ((->) r) where
    fmap = (.)

It hints a function could be applied as a functor by another function, no matter how many arguments are required. For example, (+1) is a functor, any function could be applied to it if the first argument is the same type (note that every function technically only has one input and output type).

ghci> f1 = fmap (+1) (*2)
ghci> :t f1
f1 :: Num b => b -> b
ghci> f2 = fmap (+) (*2)
ghci> :t f2
f2 :: Num a => a -> a -> a

Here, intrusion follows your mind. Any function applied to a functor(also a function) always keeps the same arguments. That's undoubtedly correct, and the following calculation outputs are logical:

ghci> f1 2
5
ghci> f2 2 1
5

More generally, in the function way to express a functor, it will be:

fmap :: Functor f => (a -> b) -> f a -> f b
fmap :: (a -> b) -> ((->) c a) -> ((->) c b)
fmap :: (a -> b) -> (c -> a) -> (c -> b)

The conduction is, after applying, the output of c-> requires an input, and the b requires the other inputs.

Functions Are Applicative Functors

As function is first class in Haskell, a function could be applied to a functor. After that, to solve the problem of applying a functor that holds a function with another functor, the applicative functor came into being.

class (Functor f) => Applicative f where
    pure :: a -> f a
    (<*>) :: f (a -> b) -> f a -> f b

To understand the <*> correctly, it's essential to know that functions are functors. Then, see the signature of <*>, which is slightly different from fmap; however, that's why the arguments could decrease.

fmap :: Functor f => (a -> b) -> f a -> f b
class (Functor f) => Applicative f where
    pure :: a -> f a
    (<*>) :: f (a -> b) -> f a -> f b

Let's see fmap firstly, the Nb refers to the number of function arguments in b, and functor f always takes 1 argument. As a result, both a->b and f b require 1+Nb arguments. However, the <*> merge two functors to one, which means the original function f (a -> b) requires 1+1+Nb arguments, but the returned value receives 1+Nb arguments. As a result, the arguments in the output minus one when applying(<*>) happened. It's lost because applicative functors are merged. This explains why the arguments decrease(from three to one).

Let's take a further step. How could the argument number decrease? Only currying could achieve it. The explanation above shows why the arguments decrease while <*> is called if we say the signature from left to right.

Now, fold the f b to f (a->b) -> f a and pass an input to f b, then the (f b) input will be f (input -> b) -> f input. As the f (a -> b ) could always be folded out, finally, the full arguments are currying one by one as the fold goes.

So, your question (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5 will be executed somehow like this:

(\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5
= (\x y -> [x , y, 2.5]) <$> (+3) <*> (*2)$ 5
= (\x -> [x, 10, 2.5] <$> (+3) $ 5
= [8, 10, 2.5]