With those caveats in mind, here's a more intensive scala-based monad tutorial I made:
https://github.com/zaboople/techknow/blob/master/scala/monad...
But really, don't burn up too much of your short life trying to come to terms with this stuff. There's a reason most languages don't get around to supporting Monads...
The whole thing about JS's Promises becomes way clearer when you see that they are a monad, except for one discrepancy (they auto-flatten themselves). It leads to much shorter and clearer code when doing pedestrian frontend stuff.
Do-notation in Haskell, or for-comprehensions in Scala are just syntax sugar for nested calls to `flatMap`, `filter`, and `map`.
I think this here shows it nicely:
https://www.baeldung.com/scala/for-comprehension#for-compreh...
In Scala you can add the needed methods to any type and than they will "magically" work in for-comprehensions. In Haskell you need to implement a Monad instance which than does the same trick.
The concrete implementations of these methods need to obey to some algebraic laws for the data structure which defines them to be called a monad. But that's pretty much it.
In my opinion all that Haskell in most "monad tutorials" just blurs an in principle very simple concept.
The in practice relevant part is that a monad can be seen as an interface for a wrapper type with a constructor that wraps some value (whether a flat value, some collection, or even functions, makes no difference), does not expose an accessor to this wrapped value, and has a `flatMap` method defined. It also inherits a `map` method, coming from an interface called "Functor". The thing is also an instance of an "Applicative", which is an interface coming with a `combine` method which takes another object of the same type as itself and returns a combination of again the same type (classical example: string concatenation can be a `combine` implementation if we'd say that `String` implements the `Applicative` interface).
M<T1>::map(f: (T1 -> T2)): M<T2>
List<int>([1, 2, 3]).map(x => toString(x)) == List<string>(["1", "2", "3"])
You can always flatten the nested structure: M<M<T>>::flatten(): M<T> // [["a", "b"], ["c", "d"]] -> ["a", "b", "c", "d"]
This is usually expressed in a different form, more fundamental: M<T1>::flatMap(f: (T1 => M<T2>)): M<T2>
List(["a b", "c d"]).flatMap(x => x.split()) == List(["a", "b", "c", "d"])
You can notice how that map() thing does looping over a sequence for you.But Optional<T> is also a monad:
let x: Optional<int> = Some(1);
let y: Optional<int> = Nothing;
x.map(n => n + 1).map(n => n * 2) == Some(4);
y.map(n => n + 1).map(n => n * 2) == Nothing;
As you see, the same map() (and flatMap()) does the condition checking for you. and can be chained safely.You can also notice how chaining of map-like operations does operation sequencing:
fetch(url).then(content => content.json()).then(data => process(data))
Your language, like JS/TS, can add some syntax sugar over it, and allow you to write it as a sequence of statements: async () => {
const response = await fetch(url);
const data = await response.json();
process(data);
}
Promises are not exactly monads though, a Promise<Promise<T>> immediately transforms into Promise<T>. But other monadic properties are still there.Minor quibble, "can only be resolved as". The runtime absolutely holds Promise<Promise<T>>'s.