To respect fast readers, I start with precise definition first,
continue with quick more "plain English" explanation, and then move to examples.
Here is a both concise and precise definition slightly reworded:
A monad (in computer science) is formally a map that:
sends every type X
of some given programming language to a new type T(X)
(called the "type of T
-computations with values in X
");
equipped with a rule for composing two functions of the form
f:X->T(Y)
and g:Y->T(Z)
to a function g∘f:X->T(Z)
;
in a way that is associative in the evident sense and unital with respect to a given unit function called pure_X:X->T(X)
, to be thought of as taking a value to the pure computation that simply returns that value.
So in simple words, a monad is a rule to pass from any type X
to another type T(X)
, and a rule to pass from two functions f:X->T(Y)
and g:Y->T(Z)
(that you would like to compose but can't) to a new function h:X->T(Z)
. Which, however, is not the composition in strict mathematical sense. We are basically "bending" function's composition or re-defining how functions are composed.
Plus, we require the monad's rule of composing to satisfy the "obvious" mathematical axioms:
- Associativity: Composing
f
with g
and then with h
(from outside) should be the same as composing g
with h
and then with f
(from inside).
- Unital property: Composing
f
with the identity function on either side should yield f
.
Again, in simple words, we can't just go crazy re-defining our function composition as we like:
- We first need the associativity to be able to compose several functions in a row e.g.
f(g(h(k(x)))
, and not to worry about specifying the order composing function pairs. As the monad rule only prescribes how to compose a pair of functions, without that axiom, we would need to know which pair is composed first and so on. (Note that is different from the commutativity property that f
composed with g
were the same as g
composed with f
, which is not required).
- And second, we need the unital property, which is simply to say that identities compose trivially the way we expect them. So we can safely refactor functions whenever those identities can be extracted.
So again in brief: A monad is the rule of type extension and composing functions satisfying the two axioms -- associativity and unital property.
In practical terms, you want the monad to be implemented for you by the language, compiler or framework that would take care of composing functions for you. So you can focus on writing your function's logic rather than worrying how their execution is implemented.
That is essentially it, in a nutshell.
Being professional mathematician, I prefer to avoid calling h
the "composition" of f
and g
. Because mathematically, it isn't. Calling it the "composition" incorrectly presumes that h
is the true mathematical composition, which it isn't. It is not even uniquely determined by f
and g
. Instead, it is the result of our monad's new "rule of composing" the functions. Which can be totally different from the actual mathematical composition even if the latter exists!
To make it less dry, let me try to illustrate it by example
that I am annotating with small sections, so you can skip right to the point.
Exception throwing as Monad examples
Suppose we want to compose two functions:
f: x -> 1 / x
g: y -> 2 * y
But f(0)
is not defined, so an exception e
is thrown. Then how can you define the compositional value g(f(0))
? Throw an exception again, of course! Maybe the same e
. Maybe a new updated exception e1
.
What precisely happens here? First, we need new exception value(s) (different or same). You can call them nothing
or null
or whatever but the essence remains the same -- they should be new values, e.g. it should not be a number
in our example here. I prefer not to call them null
to avoid confusion with how null
can be implemented in any specific language. Equally I prefer to avoid nothing
because it is often associated with null
, which, in principle, is what null
should do, however, that principle often gets bended for whatever practical reasons.
What is exception exactly?
This is a trivial matter for any experienced programmer but I'd like to drop few words just to extinguish any worm of confusion:
Exception is an object encapsulating information about how the invalid result of execution occurred.
This can range from throwing away any details and returning a single global value (like NaN
or null
) or generating a long log list or what exactly happened, send it to a database and replicating all over the distributed data storage layer ;)
The important difference between these two extreme examples of exception is that in the first case there are no side-effects. In the second there are. Which brings us to the (thousand-dollar) question:
Are exceptions allowed in pure functions?
Shorter answer: Yes, but only when they don't lead to side-effects.
Longer answer. To be pure, your function's output must be uniquely determined by its input. So we amend our function f
by sending 0
to the new abstract value e
that we call exception. We make sure that value e
contains no outside information that is not uniquely determined by our input, which is x
. So here is an example of exception without side-effect:
e = {
type: error,
message: 'I got error trying to divide 1 by 0'
}
And here is one with side-effect:
e = {
type: error,
message: 'Our committee to decide what is 1/0 is currently away'
}
Actually, it only has side-effects if that message can possibly change in the future. But if it is guaranteed to never change, that value becomes uniquely predictable, and so there is no side-effect.
To make it even sillier. A function returning 42
ever is clearly pure. But if someone crazy decides to make 42
a variable that value might change, the very same function stops being pure under the new conditions.
Note that I am using the object literal notation for simplicity to demonstrate the essence. Unfortunately things are messed-up in languages like JavaScript, where error
is not a type that behaves the way we want here with respect to function composition, whereas actual types like null
or NaN
do not behave this way but rather go through the some artificial and not always intuitive type conversions.
Type extension
As we want to vary the message inside our exception, we are really declaring a new type E
for the whole exception object and then
That is what the maybe number
does, apart from its confusing name, which is to be either of type number
or of the new exception type E
, so it is really the union number | E
of number
and E
. In particular, it depends on how we want to construct E
, which is neither suggested nor reflected in the name maybe number
.
What is functional composition?
It is the mathematical operation taking functions
f: X -> Y
and g: Y -> Z
and constructing
their composition as function h: X -> Z
satisfying h(x) = g(f(x))
.
The problem with this definition occurs when the result f(x)
is not allowed as argument of g
.
In mathematics those functions cannot be composed without extra work.
The strictly mathematical solution for our above example of f
and g
is to remove 0
from the set of definition of f
. With that new set of definition (new more restrictive type of x
), f
becomes composable with g
.
However, it is not very practical in programming to restrict the set of definition of f
like that. Instead, exceptions can be used.
Or as another approach, artificial values are created like NaN
, undefined
, null
, Infinity
etc. So you evaluate 1/0
to Infinity
and 1/-0
to -Infinity
. And then force the new value back into your expression instead of throwing exception. Leading to results you may or may not find predictable:
1/0 // => Infinity
parseInt(Infinity) // => NaN
NaN < 0 // => false
false + 1 // => 1
And we are back to regular numbers ready to move on ;)
JavaScript allows us to keep executing numerical expressions at any costs without throwing errors as in the above example. That means, it also allows to compose functions. Which is exactly what monad is about - it is a rule to compose functions satisfying the axioms as defined at the beginning of this answer.
But is the rule of composing function, arising from JavaScript's implementation for dealing with numerical errors, a monad?
To answer this question, all you need is to check the axioms (left as exercise as not part of the question here;).
Can throwing exception be used to construct a monad?
Indeed, a more useful monad would instead be the rule prescribing
that if f
throws exception for some x
, so does its composition with any g
. Plus make the exception E
globally unique with only one possible value ever (terminal object in category theory). Now the two axioms are instantly checkable and we get a very useful monad. And the result is what is well-known as the maybe monad.