7
votes

In Haskell, the default implementation of the (<*>) operator (it applies an applicative of functions a->b to an applicative of a resulting in an applicative of b) in Control.Applicative is defined as such-

(<*>) :: f (a -> b) -> f a -> f b
(<*>) = liftA2 id

and I simply can't understand how it works.

liftA2 has the type liftA2 :: (a -> b -> c) -> f a -> f b -> f c, meaning it takes a binary function, which id is not. From my understanding, this means id is somehow interpreted as some more complex type- but I'm not sure which or how it makes this definition work. If someone could explain perhaps what type is id interpreted as (what type the a in the id :: a -> a definition stands for) and also walk through how it results in a function which takes an applicative of functions and an applicative of values and applies them I would be very thankful.

1
-> is right associative. a -> b -> c means a -> (b -> c).melpomene
Note that ($)=id, only with a less general type. That code can be written more clearly as ap = liftA2 ($).chi

1 Answers

16
votes

Let's say the type of id is d -> d, so all our type variables have different names. Now let's introduce two new type variables e and t and say that d = e -> t. That makes the type of id:

id :: (e -> t) -> e -> t

Now this fits the type of the first argument to liftA2 with a = e -> t, b = e and c = t. So with those assignments, the type of liftA2 becomes:

liftA2 :: ((e -> t) -> e -> t) -> f (e -> t) -> f e -> f t

And if we apply the first argument, the remaining type becomes f (e -> t) -> f e -> f t, which is exactly the type of <*> (modulo renaming).