Home > Software design >  In Haskell how to "apply" functions in nested context to a value in context?
In Haskell how to "apply" functions in nested context to a value in context?

Time:09-21

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.

CodePudding user response:

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.

CodePudding user response:

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 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).

  • Related