While learning Scala, I came across the concept of Monads or rather, what I’d call a widespread misconception, as most articles and explanations are incomplete, misleading, and steeped in confusion.
I recall a discussion, long time ago, with a colleague who mentioned that Scala is more interesting than Java because it contains Monads. When asked about what is a Monad, no clear definition was provided but an example: "think about it as an Optional(Java8) or Either(from Vavr)".
I initially ignored the topic, but once I became a Scala developer, I decided to analyze and truly understand the concept so that I could speak about it without any ambiguity if ever asked
One thing I found out is that Monad is not a concept defined in Scala and that you have to implement it or benefit from already created ones.
In mathematics the concept is based of morphisms from one set to another, and their composition. Which is quite simplistic and at the same time highly abstract.
In programming though, you have to just start writing a for and stumble upon an embarrassing question: how many items are in a set ?
for(int i=0;i<=? ...
What if there are no items, one item, many or infinite ? Each case has to be treated separately jamming the code with if/cases and whatever other solutions out there...
What if there are different pairs of sets? Implement a for for each pair ?
Regardless of the chosen implementation it is always solving one thing, a mapping from one set to another. So if it is just about mapping from one set to another, why not making it a standard that works on any set, performs an action, returns another set and allow the process to start over?
Basically build a wrapper, it takes an array of a specific type, a function from one type to another type, and return a wrapper over another type. And it can be chained indefinitely or until the result is changed from wrapper to a value. <- the description of a monad in simple words
From here things become simple if not obvious, a construction like:
int sum = Stream.of("String 1", "String 2", "String 3", "String 4", "String 5")
.mapToInt(String::length)
.reduce(0, Integer::sum);
is a Monad in Java.
Here’s the formal (mathematical) demonstration of that:
- Monad is a functor.
- A Functor is a mapping between Categories, in simple terms it associates for any element x in Category A an element y in Category B,
- A Category is a structure that consists of a set of objects, a set of morphisms
and a binary operation defined on compatible pairs of morphisms called composition. - A category that has only one object is a Monoid. And this is the key to reverse engineer concept.
From Monoid to Monad
Monoid definition is simple
- Set
- Binary operation with a result in the same Set
- Identity element
So lets define two monoids
Monoid S
- A set of all strings
- Concatenation operation (returns a string, also a part of set)
- Identity element is ""
Monoid I
- A set of all integers
- (+) operation
- Identity element is 0
From Monoid to Category
Monoid is a particular form of Category, thus we have two categories S and I.
From Category to Functor
We define a morphism from Category S to Category I:
(String item) -> item.length
And we have the Monad
sum = Stream.of("String 1", "String 2", "String 3", "String 4", "String 5")
.mapToInt(String::length)
.reduce(0, Integer::sum);
Problems that monads address
Simple for
for(every i in list) {
doSomeStuff
}
Written in a monadic way:
list.forEachElementDo(doSomeStuff)
Chaining fors
Supposedly there is this list
parentList = List[List[B]]
The generic problem is to doSomeStuff on each element.
for(every i in parentList ) {
for(every j in childList[i]){
doSomeStuff
}
}
Written in a monadic way:
parentList.forEachElementDo(childList -> childList.forEachElementDo(doSomeStuff))
It is clear now that the result is flattened(into doSomeStuff return type) therefore the flatMap function used in monads.
Thus, in a conventional manner, the code becomes:
parentList.flatMap(childList -> childList.flatMap(someStuff))
Sequencing fors
If you sequence "fors" as:
parentList.flatMap(doFirstStuff).flatMap(doSecondStuff)
You basically "monaded" this code:
for(int i=0; i<....){
doFirstStuff
}
for(int j=0; j<....){
doSecondStuff;
}
Conclusion
I approach monads from the perspective of the problems they solve rather than what they represent. It simplifies the concept and makes it easier to grasp intuitively, without getting lost in the mathematical formalism, which is often difficult to internalize and tends to create confusion.
This explanation is presented in the simplest way possible, which might seem almost unbelievable to those who have invested considerable effort in explaining monads through more complex approaches. Apologies if this comes across as a slight on anyone’s expertise. If so, please feel free to point out any mistakes in the explanation or share additional examples of problems that monads help solve.
Sources
Monad:
https://en.wikipedia.org/wiki/Monad_(functional_programming)
Functor:
https://en.wikipedia.org/wiki/Functor#endofunctor
Category:
https://mathworld.wolfram.com/Category.html
https://en.wikipedia.org/wiki/Category_(mathematics)
Monoid:
https://en.wikipedia.org/wiki/Monoid#Monoid_homomorphisms
Top comments (0)