Let me explain my understanding without going into category theory:
Functors and monads both provide some tool to wrapped input, returning a wrapped output.
Functor = unit + map (i.e. the tool)
where,
unit
= something which takes raw input and wraps it inside a small context.
map
= the tool which takes a function as input, applies it to raw value in wrapper, and returns wrapped result.
Example: Let us define a function which doubles an integer
// doubleMe :: Int a -> Int b
const doubleMe = a => 2 * a;
Maybe(2).map(doubleMe) // Maybe(4)
Monad = unit + flatMap (or bind or chain)
flatMap
= the tool which flattens the map
, as its name implies. It will be clear soon with the example below.
Example: Let us say we have a curried function which appends two strings only if both are not blank.
Let me define one as below:
append :: (string a,string b) -> Maybe(string c)
Let's now see the problem with map
(the tool that comes with Functor
),
Maybe("a").map(append("b")) // Maybe(Maybe("ab"))
How come there are two Maybe
s here?
Well, that's what map
does; it applies the provided function to the wrapped value and wraps the result.
Let's break this into steps,
Apply the mapped function to the wrapped value
; here the mapped function is append("b")
and the wrapped value is "a"
, which results in Maybe("ab")
.
Wrap the result, which returns Maybe(Maybe("ab"))
.
Now the value we are interested in is wrapped twice. Here comes flatMap
to the rescue.
Maybe("a").flatMap(append("b")) // Maybe("ab")
Of course, functors and monads have to follow some other laws too, but I believe this is not in the scope of what is asked.