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.