Deriving not-so-complex types
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
(<*>)
tofmap
, 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!