3

In Haskell, the Functor has a function fmap which the type of it is:

ghci> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b

This makes sense to me that fmap lifts a function from the type of a -> b to f a -> f b.

Then I am curious about what is the type of fmap fmap, so I tried and got something weird to me:

ghci> :t fmap fmap
fmap fmap
  :: (Functor f1, Functor f2) => f1 (a -> b) -> f1 (f2 a -> f2 b)

Hmm, this type is somewhat complicate, but I can explain it by replacing a with a -> b and b with f2 a -> f2 b.

Then, I wanted to one step further:

fmap fmap fmap
  :: (Functor f1, Functor f2) => (a -> b) -> f1 (f2 a) -> f1 (f2 b)

Oh, wait! Things go to be fun when putting 3 fmap together. How to explain this?

Could someone help to explain how could I derive the type of fmap fmap fmap?

Hao Yang
  • 33
  • 2
  • Does this answer your question? [How (fmap . fmap) typechecks](https://stackoverflow.com/questions/23030638/how-fmap-fmap-typechecks) – Bergi Feb 13 '23 at 09:18
  • See also https://stackoverflow.com/questions/30153728/haskell-fmap-fmap-function-function-over-two-functors, https://stackoverflow.com/questions/8736995/fun-with-repeated-fmap, https://stackoverflow.com/questions/28825354/how-does-fmap-fmap-apply-to-functions-as-arguments and https://stackoverflow.com/questions/69432210/return-type-of-fmap-fmap-func-vs-fmap-fmap-func – Bergi Feb 13 '23 at 09:19
  • 2
    If you workout the types you get that `f1` in `fmap fmap` becomes the function functor. that is: `(->) (a -> b)` in `fmap fmap fmap` – lsmor Feb 13 '23 at 09:30
  • `fmap g x` requires `x :: f a`. Now in your case, `g=fmap :: ...` (which does not matter right now), and `x :: (t->u)-> f2 t -> f2 u`. Hence we need the type equality `f a = (t->u) -> f2 t -> f2 u` which, if we properly write type constructors in prefix form, becomes `f a = (->) (t->u) (f2 t -> f2 u)`. We then get `f = (->) (t->u)` and `a = `f2 t -> f2 u`. I think you can continue from here, now taking into account the type of `g=fmap`. – chi Feb 13 '23 at 10:23
  • 1
    See also https://discourse.haskell.org/t/a-little-curiosity/3829 – michid Feb 13 '23 at 10:26

2 Answers2

5

For clarity, let's introduce

fmapA, fmapB, fmapC :: Functor f => (a -> b) -> f a -> f b
fmapA = fmapB = fmapC = fmap

and consider

   fmapA fmapB fmapC :: ?

Forget about fmapB for a bit, start with fmapA _ fmapC. You're treating fmapC on the right as a container here, over which you map something. Does that make sense? Well, look at the type in non-infix form. Recall that x -> y -> z is the same as x -> (y -> z), and p -> q is the same as ((->) p) q, thus

fmapC :: ((->) p) q where {p ~ (a->b), q ~ (f a->f b)}

To use this as a container type, the f in fmapA's signature needs to unify with (->) p. That's the function functor. So, despite having three polymorphic fmaps here, one of the functors is already predetermined by the expression. Therefore, it would be better to immediately resolve the polymorphism that only makes it more difficult to understand, and replace it with the definition of that particular functor instance, which turns out to be rather simple:

instance Functor ((->) a) where
  fmap = (.)

So, that reduces our expression to (.) fmapB fmapC – or, as it's preferrably written,

   fmapB . fmapC

Which is a far more sensible thing to write in actual code, and has been discussed previously on StackOverflow.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
0
{-# Language BlockArguments      #-}
{-# Language ScopedTypeVariables #-}
{-# Language TypeApplications    #-}

fffmap
  :: forall f g a b. ()
  => Functor f
  => Functor g
  => (a -> b)
  -> (f (g a) -> f (g b))
fffmap = fmap fmap fmap

A polymorphic function takes a type as an argument. The forall. quantifiee is invisible and implicitly solved by unification but we can explicitly instantiate it with a type application @...

I use block arguments which allows me to write fmap fmap fmap as

  do fmap 
  do fmap 
  do fmap

just to make it clearer. This is how they are actually instantiated:

fffmap
  :: forall f g a b. ()
  => Functor f
  => Functor g
  => (a -> b)
  -> (f (g a) -> f (g b))
fffmap =
  do fmap @((->) (a -> b)) @(g a -> g b) @(f (g a) -> f (g b))
  do fmap @f               @(g a)        @(g b)
  do fmap @g               @a            @b

The first fmap = (.) is instantiated to the reader monad (.. ->), no wonder you find it complicated. If you look at the type of f1, it IS complicated.

fffmap
  :: forall f g a b.
     Functor f
  => Functor g
  => (a -> b)
  -> (f (g a) -> f (g b))
fffmap = f1 f2 f3 where

  f1 :: ((g a -> g b) -> f (g a) -> f (g b)) -> ((a -> b) -> g a -> g b) -> (a -> b) -> f (g a) -> f (g b)
  f1 = fmap

  f2 :: (g a -> g b) -> (f (g a) -> f (g b))
  f2 = fmap

  f3 :: (a -> b) -> (g a -> g b)
  f3 = fmap
Iceland_jack
  • 6,848
  • 7
  • 37
  • 46