4
nestedApply :: (Applicative f, Applicative g) => g (f (a -> b)) -> f a -> g (f b)

As the type indicates, how to get that (a->b) applied to that a in the context f?

Thanks for help.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Chaot1c
  • 67
  • 5
  • Hint: you only need `(Applicative f, Functor g)`. – dfeuer Sep 17 '21 at 04:32
  • First you need to lift an Applicative over the outer structure `g` so to get `f (a -> b)` into `f a -> fb`. HINT: `fmap (<*>) gfab`, `pure fa ~ g(fa)` – Khundragpan Sep 17 '21 at 04:56
  • @Khundragpan Thank you for your help. I'm not sure whether I understand what you said. If I fmap (<*>) to gfab then I will get <*> applied to fab right? But what should I do next? Why I need to add a g to fa? – Chaot1c Sep 17 '21 at 05:10
  • @Chaot1c `nestedApply gfab fa = (<*>) <$> gfab <*> pure fa` – Khundragpan Sep 17 '21 at 05:20
  • @Khundragpan Thanks a lot for your help! Now I understand. I'm new to Haskell so the concept here is extremely confusing for me. – Chaot1c Sep 17 '21 at 06:00

2 Answers2

5

This one of those cases where it's helpful to focus on types. I will try to keep it simple and explain the reasoning.

Let's start with describing the task. We have gfab :: g(f(a->b)) and fa :: f a, and we want to have g(f b).

gfab :: g (f (a -> b))
fa   :: f a
??1  :: g (f b)

Since g is a functor, to obtain type g T we can start with a value ??2 of type g U and apply fmap to ??3 :: U -> T. In our case, we have T = f b, so we are looking for:

gfab :: g (f (a -> b))
fa   :: f a
??2  :: g U
??3  :: U -> f b
??1 = fmap ??3 ??2  :: g (f b)

Now, it looks like we should pick ??2 = gfab. After all,that's the only value of type g Something we have. We obtain U = f (a -> b).

gfab :: g (f (a -> b))
fa   :: f a
??3  :: f (a -> b) -> f b
??1 = fmap ??3 gfab :: g (f b)

Let's make ??3 into a lambda, \ (x :: f (a->b)) -> ??4 with ??4 :: f b. (The type of x can be omitted, but I decided to add it to explain what's going on)

gfab :: g (f (a -> b))
fa   :: f a
??4  :: f b
??1 = fmap (\ (x :: f (a->b)) -> ??4) gfab :: g (f b)

How to craft ??4. Well, we have values of types f (a->b) and f a, so we can <*> those to get f b. We finally obtain:

gfab :: g (f (a -> b))
fa   :: f a
??1 = fmap (\ (x :: f (a->b)) -> x <*> fa) gfab :: g (f b)

We can simplyfy that into:

nestedApply gfab fa = fmap (<*> fa) gfab

Now, this is not the most elegant way to do it, but understanding the process is important.

chi
  • 111,837
  • 3
  • 133
  • 218
2

With

nestedApply :: (Applicative f, Applicative g) 
            => g (f (a -> b))  
            ->    f  a 
            -> g (f       b )

to get that (a->b) applied to that a in the context f, we need to operate in the context g.

And that's just fmap.

It's clearer with the flipped signature, focusing on its last part

flip nestedApply :: (Applicative f, Applicative g) 
            =>    f  a 
            -> g (f (a -> b))     --- from here
            -> g (f       b )     --- to here

So what we have here is

nestedApply gffun fx = fmap (bar fx) gffun

with bar fx being applied under the g wraps by fmap for us. Which is

bar fx ::         f (a -> b)
            ->    f       b   

i.e.

bar ::            f  a
            ->    f (a -> b)
            ->    f       b   

and this is just <*> isn't it, again flipped. Thus we get the answer,

nestedApply gffun fx  =  fmap (<*> fx) gffun

As we can see only fmap capabilities of g are used, so we only need

nestedApply :: (Applicative f, Functor g) => ...

in the type signature.


It's easy when writing it on a sheet of paper, in 2D. Which we imitate here with the wild indentation to get that vertical alignment.

Yes we the humans learned to write first, on paper, and to type on a typewriter, much later. The last generation or two were forced into linear typing by the contemporary devices since the young age but now the scribbling and talking (and gesturing and pointing) will hopefully be taking over yet again. Inventive input modes will eventually include 3D workflows and that will be a definite advancement. 1D bad, 2D good, 3D even better. For instance many category theory diagrams are much easier to follow (and at least imagine) when drawn in 3D. The rule of thumb is, it should be easy, not hard. If it's too busy, it probably needs another dimension.

Just playing connect the wires under the wraps. A few self-evident diagrams, and it's done.


Here's some type mandalas for you (again, flipped):

-- <$>               -- <*>               -- =<<
f     a              f     a              f     a       
     (a -> b)        f    (a -> b)             (a -> f b)
f          b         f          b         f    (     f b)   -- fmapped, and
                                          f            b    -- joined

and of course the mother of all applications,

--      $
      a
      a -> b
           b

a.k.a. Modus Ponens (yes, also flipped).

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • You only need `Functor g`, as I pointed out in a comment. – dfeuer Sep 17 '21 at 17:12
  • @dfeuer perhaps we can add this as a final remark... I was more playing with the OP code, trying to show them how. I don't feel it is very important though, perhaps that's the signature they were given, so naturally Applicative is already a Functor. – Will Ness Sep 17 '21 at 17:25
  • 1
    @dfeuer of course it's superfluous. maybe I'll edit it in later (I don't like it when it says "...edited" instead of "...answered" in the feed, I feel with "answered" there's more chance someone will look at it at all and with "edited" there's less chance for that ... just a feeling I have) – Will Ness Sep 17 '21 at 17:28