In Haskell, you can use do notation:
do
x <- someFunction
y <- anotherFunction x
z <- yetOneMoreFunction y x
return (x + y + z)
This is syntactic sugar for something like (using `flatMap` in place of >>= (bind) ): flatMap someFunction (\x ->
anotherFunction x (\y ->
yetOneMoreFunction y x -> (\z ->
return (x + y + z)
)
)
)
If Haskell programmers had to write code like this (which is what monads look like in other languages), there's no way they would use monads!In languages with objects, you can sometimes get away a minor victory by using what I'll call a "shallow bind". Consider promises in javascript (which form a monad with unit a = Promise.resolve(a) and flatMap p f = p.then(f) ):
someHttpRequest().then( response => {
if (response.body == undefined) {
return Promise.reject("no body error");
} else {
return Promise.resolve(response.body);
}
}).then(body => {
try {
return Promise.resolve(JSON.parse(body));
} catch (e) {
return Promise.reject(e);
}
}).then(parsed => {
if (parsed.status == undefined) {
return Promise.reject("no status");
} else if (parsed.status == 500) {
return Promise.reject("server failure");
} else {
return Promise.resolve(data);
}
// what would you do if you decided you wanted to reference `response` here somewhere?
});
This is definitely an improvement over a deeply nested callback style from 10 years ago, but it does sacrifice some flexibility. You can't refer to previously handled promises because the callbacks are shallow. You can do that with do-notation, and that's what makes the concept of monads stick in Haskell.Once you have that, and in Haskell, it's amazingly implemented as an overloadable piece of syntax, the doors are wide open. Example: at my work, we use free monads to get a similar effect as dependency injection in other languages (clean separation of concerns, easy mocking, etc) but with two huge benefits: 1. Any function is limited to using only the dependencies/effects you allow it to, it won't type check if you try to sneak something in, and 2. Because of the overloadable syntax, we use it to collect timing info on all of our operations that are doing IO in a way where we get a flame graph style hierarchy of effects _for free_. We never have to specify when to start and a stop the timers, the syntax knows when to. It's pretty powerful.