简体   繁体   中英

Applicative functors: why can fmap take a function with more than one argument?

I am getting into Haskell and found the book "learn you a Haskell" most helpful. I am up to the section on applicative functors .

I am puzzled by the following as it appears in the book:

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

which yields the output:

[8.0,10.0,2.5]

First of all, I have confirmed my suspicion in ghci in regards to precedence of the operators, so that the above equals the following ugly statement:

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

So from that it becomes clear that the first thing that happens is the fmap call via the (<$>) infix operator.

And this is the core of what boggles my mind currently. The definition of fmap (here shown as infix (<$>) ) is:

(<$>) :: (Functor f) => (a -> b) -> f a -> f b

But in the equation I am struggling with, (\\xyz -> [x, y, z]) takes three arguments, not just one. So how could the first argument of type (a -> b) be satisfied?

I think it might have to do with partial application / currying but I cannot figure it out. I would greatly appreciate an explanation. Hope I have formulated the question well enough.

Simple answer: there are no functions with multiple arguments in Haskell!

There are two candidates for what you might call "dyadic function": a function that takes a (single!) tuple, and – by far prevalent in Haskell – curried functions . Those take just one argument, but the result is a function again.

So, to figure out what eg fmap (+) does, let's write

type IntF = Int -> Int

-- (+) :: Int -> IntF
-- fmap :: ( a -> b  ) ->  f a -> f b
--  e.g.:: (Int->IntF) -> f Int->f IntF

Test it yourself in GHCi:

Prelude> type IntF = Int -> Int
Prelude> let (#) = (+) :: Int -> IntF
Prelude> :t fmap (#)
fmap (#) :: Functor f => f Int -> f IntF

Consider a function of type

f :: a -> b -> c -> d

where d is any other type. Due to currying, this can be thought of as a function with the following type

f :: a -> (b -> c -> d)

ie a function that takes an a and returns function of type b -> c -> d . If you apply fmap , you have

-- the type of fmap, which is also :: (a -> r) -> (f a -> f r)
fmap :: Functor f => (a -> r) -> f a -> f r

-- the type of f
f :: a -> (b -> c -> d)

-- so, setting r = b -> c -> d
fmap f :: f a -> f (b -> c -> d)

Which is now of the right type to be used as the left-hand argument to (<*>) .

Because you can take a 3-argument function, feed it just one argument, and this results in a 2-argument function. So you're going to end up with a list of 2-argument functions. You can then apply one more argument, ending up with a list of 1-argument functions, and finally apply the last argument, whereupon you end up with a list of ordinary numbers.

Incidentally, this is why Haskell has curried functions. It makes it easy to write constructs like this one which work for any number of function arguments. :-)

I personally find the applicative functor instance for functions a bit strange. I'll walk you through this example to try to understand intuitively what's going on:

>>> :t (\x y z -> [x, y, z]) <$> (+3)
... :: Num a => a -> a -> a -> [a]
>>> ((\x y z -> [x, y, z]) <$> (+3)) 1 2 3
[4,2,3]

This applies (+3) to the first parameter of the inner function. The other 2 outer parameters are passed to the inner function unmodified.

Let's add an applicative:

>>> :t (\x y z -> [x, y, z]) <$> (+3) <*> (*2)
... :: Num a => a -> a -> [a]
>>> ((\x y z -> [x, y, z]) <$> (+3) <*> (*2)) 1 2
[4,2,2]

This applies (+3) to the first argument as before. With the applicative, the first outer parameter ( 1 ) is applied (*2) and passed as the second parameter of the inner function. The second outer parameter is passed unmodified to the inner function as its third parameter.

Guess what happens when we use another applicative:

>>> :t (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2)
... :: Fractional a => a -> [a]
>>> (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 1
[4.0,2.0,0.5]

3 applications to the same parameter passed as 3 arguments to the inner function.

It's not theoretically solid explanation, but it can give an intuition about how the applicative instance of functions works.

Background

Let's start with the definition of the <*> and pure for functions as an instance of Applicative . For pure , it will take any garbage value, and return x . For <*> , you can think of it as applying x to f , getting a new function out of it, then applying it to the output of gx .

instance Applicative ((->) r) where  
    pure x = (\_ -> x)  
    f <*> g = \x -> f x (g x) 

Now, let's look at the definition of <$> . It is just an infix version of fmap .

(<$>) :: (Functor f) => (a -> b) -> f a -> f b  
f <$> x = fmap f x  

Recall that fmap has the following implementation:

instance Functor ((->) r) where  
    fmap f g = (\x -> f (g x))  

Proving that f <$> x is just pure f <*> x

Let's start with pure f <*> x . Replace pure f with (\\_ -> f) .

pure f <*> x 
= (\_ -> f) <*> x

Now, let's apply the definition of <*> , which is f <*> g = \\q -> fq (gq) .

(\_ -> f) <*> x
= \q -> (\_ -> f) q (x q)

Notice we can simplify (\\_ -> f) q as just f . The function takes in whatever value we give it, and returns f .

\q -> (\_ -> f) q (x q)
= \q -> f (x q)

That looks just like our definition of fmap ! And the <$> operator is just infix fmap .

\q -> f (x q)
= fmap f x
= f <$> x

Let's keep this in mind: f <$> g is just pure f <*> g .

Understanding (\\xyz -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5

First step is to rewrite the left side of expression to use <*> instead of <$> . Using what we just proved in in the previous section:

(\x y z -> [x, y, z]) <$> (+3)
= pure (\x y z -> [x, y, z]) <*> (+3)

So the full expression becomes

pure (\x y z -> [x, y, z]) <*> (+3) <*> (*2) <*> (/2) $ 5

Let's simplify the first operator using the definition of <*>

pure (\x y z -> [x, y, z]) <*> (+3)
= \a -> f a (g a) --substitute f and g
= \a -> pure (\x y z -> [x, y, z]) a ((+3) a)

Now let's substitute pure x with (\\_ -> x) . Observe that a becomes the garbage value that's used as _ , and is consumed to return the function (\\xyz -> [x, y, z]) .

\a -> (\_-> (\x y z -> [x, y, z])) a ((+3) a)
= \a -> (\x y z -> [x, y, z]) ((+3) a)

Now let's look back at the full expression, and tackle the next <*> . Again, let's apply the definition of <*> .

(\a -> (\x y z -> [x, y, z]) ((+3) a)) <*> (*2)
= \b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)

Finally, let's repeat this one last time for the final <*> .

(\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) <*> (/2)
= \c -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) c ((/2) c)

Notice that it's a function that takes a single value. We'll feed it 5 .

(\c -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) c ((/2) c)) 5
(\5 -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) 5 ((/2) 5))
       (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) 5 (2.5   )
       (\5 -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) 5 ((*2) 5))   (2.5   )
              (\a -> (\x y z -> [x, y, z]) ((+3) a)) 5 (10    )    (2.5   )
              (\5 -> (\x y z -> [x, y, z]) ((+3) 5))   (10    )    (2.5   )           
                     (\x y z -> [x, y, z]) (8     )    (10    )    (2.5   )         

(\x y z -> [x, y, z]) (8) (10) (2.5)                     
= [8, 10, 2.5]

And that's how we get the final answer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM