Deriving not-so-complex types

Arvind Devarajan
Techscape
Published in
4 min readMar 12, 2019

--

(Wonderful image was modified from: https://openclipart.org/detail/123259/professeur-teacher)

Often times, I get to read Haskell code with these patterns: (fmap . fmap), fmap (<*>), etc. I’ve just taken three of these types, and break these into their intuitions here in the hopes that somebody out there can understand when to use these.

First we go with plain derivations of the final outcome, then we talk about the intuition behind, so that one does not need to derive these to understand them.

fmap (<*>)

What we are doing here is applying the function fmap to another function <*>. Lets start from the first principles:

(<*>) :: (Applicative f1) => f1 (a -> b) -> f1 a -> f1 b ----- (1)

re-writing, with grouping parenthesis:

(<*>) :: (Applicative f1) => (f1 (a -> b)) -> (f1 a -> f1 b) -- (1a)

fmap :: (Functor f2) => (c -> d) -> f2 c -> f2 d ----- (2)

For fmap (<*>), since fmap is applied to (<*>):

Applying (<*>) to fmap, by substituting:

c ==> f1 (a -> b)
d ==> (f1 a -> f1 b)

in fmap:

fmap :: -- Applying (<*>), by substituting c and d
(f1 (a -> b) -> (f1 a -> f1 b)) ->
-- Result
f2 (f1 (a -> b)) -> f2 (f1 a -> f1 b) ----- (3)

Therefore, fmap (<*>) is the result from above:

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

fmap . fmap

This one is got straight from here: https://stackoverflow.com/questions/40055333/applying-compose-to-fmap

First, we start with definitions of fmap from the first principles. Just to make it easy, we elaborate both fmaps:

fmap :: (Functor f1) => (x -> y) -> f1 x -> f1 y      ----- (1)
fmap :: (Functor f2) => (a -> b) -> f2 a -> f2 b ----- (2)
(.) :: (a1 -> a2) -> (r -> a1) -> (r -> a2) ----- (3)

Re-writing fmap with grouped parenthesis:

fmap :: (Functor f1) => (x -> y) -> (f1 x -> f1 y)    ----- (1a)
fmap :: (Functor f2) => (a -> b) -> (f2 a -> f2 b) ----- (2a)

What we want is:

(.) fmap fmap

In (.), substituting:

From the first fmap and the first operand to (.), i.e. (a1 -> a2):

a1 ==> (x -> y)       ----- (4)
a2 ==> (f1 x -> f1 y) ----- (5)

From the second fmap and the second operand to (.), i.e. (r -> a1):

r ==> (a -> b)        ----- (6)
a1 ==> (f2 a -> f2 b) ----- (7)

From (4) and (7), we have:

x ==> f2 a            ----- (8) 
y ==> f2 b ----- (9)

Therefore, putting all of these in (3), we have:

(.) :: (f2 a -> f2 b) -> (f1 x -> f1 y) -> (r -> (f2 a -> f2 b)) -> (r -> (f1 x -> f1 y))

Further substituting x, y and r:

(.) :: -- Applying first fmap:
(f2 a -> f2 b) -> (f1 (f2 a) -> f1 (f2 b)) ->

-- Applying second fmap
((a -> b) -> (f2 a -> f2 b)) ->

-- Result
((a -> b) -> (f1 (f2 a) -> f1 (f2 b)))

Hence, the final result is:

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

(fmap . fmap) (<*>)

Now, we come to the last of the (not-so) complex types we deal here. Starting with what we know:

(fmap . fmap) :: (Functor f2, Functor f3) => (x -> y) -> f2 (f3 x) -> f2 (f3 y)  ----- (1)
(<*>) :: (Applicative f1) => f1 (a -> b) -> f1 a -> f1 b ----- (2)

Re-writing via regrouping:

(<*>) :: (f1 (a -> b)) -> (f1 a -> f1 b)        ----- (2a)

Since we are applying (fmap . fmap) to (<*>) as the first argument, substituting (from (1) and (2a)):

x ==> f1 (a -> b)
y ==> f1 a -> f1 b
(fmap . fmap) :: -- First fmap
f1 (a -> b) ->
-- Second fmap
f1 a -> f1 b ->
-- Result
f2 (f3 (f1 (a -> b))) -> f2 (f3 (f1 a -> f1 b))

Therefore, result of application:

(fmap . fmap) (<*>) :: (Applicative f1, Functor f2, Functor f3) => f2 (f3 (f1 (a -> b))) -> f2 (f3 (f1 a -> f1 b))

Intuition and examples

Now that we have these derivations in place, we need some intuitions on when to use these. We need examples, and we need practical situations for these.

The concept of “Monad Transformers” is completely based on these results, and this will be the subject of my next article.

Until then, keep the claps flowing in!

--

--