4
votes

fmap.fmap allows us to go "two layers deep" into a functor:

fmap.fmap :: (a -> b) -> f (g a) -> f (g b)

Is this also possible for applicative functors? Let's say I wanted to combine Just (+5) and [1,2,3] by using their applicative properties. I can think of an obvious way to do it, but it doesn't seem that trivial to me.

(<*>).(<*>) doesn't a have a conclusive type signature:

((<*>).(<*>)) :: (a1 -> a2 -> b) -> ((a1 -> a2) -> a1) -> (a1 -> a2) -> b
-- where I would expect something like:
-- ((<*>).(<*>)) :: f (g (a -> b)) -> f (g a) -> f (g b)

Is it possible to compose Just (+5) and [1,2,3] in this fashion?

EDIT:

The first step would be to go with either:

  • pure $ Just (+5) and fmap pure [1,2,3], or
  • fmap pure (Just (+5) and pure [1,2,3]

But I still don't how to compose these...

EDIT:

It would be nice to have a general way to compose a function f (g (a -> b) and f (g a), I'm not just looking for a solution for the above case, which is just supposed to serve as an example input of such a function. Basically I want a function:

(<***>) :: f (g (a -> b)) -> f (g a) -> f (g b)
2
f (g a) is a nested functor. But what you have is two separate, unrelated functors. You'll have to convert one to the other.Sebastian Redl
It depends on what you want to get. A list of maybes?n. 1.8e9-where's-my-share m.
If you want to map a list to a list, you can use just its Functor instance, it doesn't have to be an Applicative. Thus you want a fmap on the list side, not a <*>. You also want to insert pure in there somewhere to get Just Integer from Integer. fmap ((<*>) (Just (+5)) . pure) [1,2,3] does the job.n. 1.8e9-where's-my-share m.
Another option would be to convert a Maybe to a List (this is specific to these types and cannot be generalised to any Applicative): (maybeToList (Just (+ 5))) <*> [1,2,3].n. 1.8e9-where's-my-share m.
If you want to apply f (g (a -> b) to f (g a) you may want to give a better example of it in the question. Just (+5) and [1,2,3] are not of these types.n. 1.8e9-where's-my-share m.

2 Answers

4
votes

liftA2 has a similar compositional property as fmap.

liftA2 f            ::    f a  ->    f b  ->    f c
(liftA2 . liftA2) f :: g (f a) -> g (f b) -> g (f c)

So you can write

(liftA2 . liftA2) ($) (pure (Just (+5))) (fmap pure [1,2,3]) :: [Maybe Integer]

i.e., (<***>) = (liftA2 . liftA2) ($). (much like (<*>) = liftA2 ($))


Another way to look at it is that the composition of applicative functors is an applicative functors, this is made concrete by Data.Functor.Compose:

{-# LANGUAGE ScopedTypeVariables, PartialTypeSignatures #-}

import Data.Functor.Compose
import Data.Coerce

(<***>) :: forall f g a b. (Applicative f, Applicative g)
        => f (g (a -> b)) -> f (g a) -> f (g b)
(<***>) = coerce ((<*>) :: Compose f g (a -> b) -> _)

The point with coerce is to show that (<***>) is the applicative (<*>) for the right type; we can also do the unwrapping manually

f <***> x = getCompose $ Compose f <*> Compose x
2
votes

We have a f (g (a->b)). To get g a -> g b from g (a->b) we just need <*>, but g (a->b) is wrapped in f. Luckily f is a Functor so we can fmap over it.

Prelude> :t fmap (<*>)
fmap (<*>)
  :: (Functor f1, Applicative f) =>
     f1 (f (a -> b)) -> f1 (f a -> f b)
Prelude>

That's better, we have a function wrapped in a Functor now. If this Functor happens to be an Applicative, we can apply <*> through it.

Prelude> :t (<*>) . fmap (<*>)
(<*>) . fmap (<*>)
  :: (Applicative f, Applicative f1) =>
     f1 (f (a -> b)) -> f1 (f a) -> f1 (f b)
Prelude>

Just what the doctor ordered.

Prelude> let (<***>) = (<*>) . fmap (<*>)
Prelude> [Just (+2), Just (*3), Nothing] <***> [Just 7, Just 42, Nothing]
[Just 9,Just 44,Nothing,Just 21,Just 126,Nothing,Nothing,Nothing,Nothing]
Prelude>