My notes on Functor, Applicative and Monad
§Functor
This post provides vivid explanation for Functors in Haskell. However, I still find it confusing why we have functors in Haskell.
In order to really understand functors, we must explore the origin of this concept. According to wikipedia, “a functor is a type of mapping between categories”, but “mapping” relation isn’t very obvious to me.
The signature of fmap
is:
1 | fmap :: (a->b) -> f a -> f b |
If we rewrite it to be (the parentheses was implicit before):
1 | fmap :: (a->b) -> (f a -> f b) |
we could interpret that fmap
takes one argument, one function with type a->b
, and convert it into another function on new types (new domain), f a -> f b
. Now the “mapping” referred in the definition becomes explicit.
Intuitively, I understand types in Functor
class as containers, that could hold some value.
§Monad
All monads in Haskell are type constructors, but not all type constructors are monads. As we will see, monads have to be type constructors for which specific operations are defined and for which specific “monad laws” hold.
return
and >>=
(bind) are the two passages connecting the pure and impure worlds. Looking at the type of >>=
:
(>>=) :: m a -> (a -> m b) -> m b
We can be sure there’s some “unpacking” done in the middle, and how this “unpack” happens is defined by each instance of “Monad” type class.
Many people mistakenly believe that the only reason for having monads in Haskell is to handle non-functional computations i.e. ones that do (file or terminal) I/O, alter global variables, etc. And yet, here I showed you a monadic computation which can be done perfectly well without monads. In this case, monads are not essential; they’re just very convenient. And that’s why I said that even though the original reason for adding monads to Haskell had to do with dealing with inherently non-functional kinds of computations (like computations involving I/O), they turned out to have a far greater applicability. That’s why they’re neat.
f :: Int -> Maybe Int
f x = if x `mod` 2 == 0 then Nothing else Just (2 * x)
g :: Int -> Maybe Int
g x = if x `mod` 3 == 0 then Nothing else Just (3 * x)
h :: Int -> Maybe Int
h x = if x `mod` 5 == 0 then Nothing else Just (5 * x)
k = f >=> g >=> h
k x = case f x of
Nothing -> Nothing
Just y -> case g y of
Nothing -> Nothing
Just z -> h z
Given (>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
, it’s obvious how it could be constructed using bind
above.
Since Monad
is a subclass of Functor
, it could be understood as containers, where sequencing operations are defined.
§Applicative vs Monad
Applicative
is something between Functor
and Monad
; it’s a subclass of Functor
, and the parent class of Monad
. For Applicative
types,
sequencing operations are defined as well, so it’s a bit sutble to pinpoint the difference between Applicative
and Monad
.
The following example illustrates that Monad
support “short-circus” while Applicative
would do the function application anyway.
import Control.Applicative (pure, (<*>))
monad_f :: Int -> Maybe Int
monad_f x = return x
monad_f' :: Int -> Maybe Int
monad_f' x = undefined
applicative_f :: Maybe (Int -> Int)
applicative_f = return id
applicative_f' :: Maybe (Int -> Int)
applicative_f' = undefined
x :: Maybe Int
x = Nothing
main = do
print $ x >>= monad_f
print $ x >>= monad_f'
print $ applicative_f <*> x
print $ applicative_f' <*> x
The output is:
Nothing
Nothing
Nothing
test.hs: Prelude.undefined