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.
flatMap
(orbind
orchain
or however you want to call it). If you chose a particular language, it would be easier to explain, as the purely conceptual answer is category theory. – Bergiunit
injection function despite be able to make an admittedly vacuousmap
function. Similar tricks can be played if the type parameter of the type constructor is not used at the term-level. – Alec