Monads are so easy. That's why there are 2,000 articles online explaining how easy they are. :)
Okay hear me out:
I don't like exception handling, because it doesn't feel "pure" and consistent with the rest of my program flow. If any errors happen, I want to know what they are so I can plan ahead. You might say "well, just catch your exceptions dammit". But see, what if I forget to catch an exception right away and it propagates? Also, other than Swift, I don't know of any programming language that let's me explicitly mention if a function could throw an exception. In Python, for example, you can't just do Optional[str], because returning None is different from raising an exception. So by just looking at a function type signature, one can't know if it throws, which means you never know if you should use try/catch or not.
But let's say you take care of all edge cases and return None in those bad paths instead of raising exceptions. Let's also assume that Python does't suck and it never raises exceptions by its own standard libraries (e.g., json throws). The problem is: How do we know if the function returned None because the evaluation was None, or because it failed at some point?
So a better approach is to explicitly say the function returns "something" which may or may not be there. If it's there, then the function worked correctly, even if the value is None. If it's not there, then the function failed. Go does this. Rust does this better. Haskell has had this since ages thanks to Monads.
I like to think of monads as wrappers around data. In Python, I simply write a Result monad (similar to Rust). In Haskell, this is called a Maybe monad.
> Also, other than Swift, I don't know of any programming language that let's me explicitly mention if a function could throw an exception.
Checked exceptions in Java. I both like and hate them.
They force the function to be explicit about what exceptions they can raise. If you call a function that raises a checked exception, you must either explicitly handle the exception, or mark that it is propagated up by adding the same exception to the calling method signature.
However, they cause a lot of pain for working with higher order methods (map, filter, flatmap, etc). Because they change the signature of the method (and therefore the interface it can satisfy), you need to use a lot of generic variants of higher order methods to accept them, or like a lot of libraries, you end up writing wrapper functions that convert the checked exceptions into unchecked runtime exceptions, such that they don't modify the method signatures. This then leads to a lot of weird code, and uncaught exceptions at runtime, taking down the application.
The problem here isn't checked exceptions as such, it's that they are not first-class, so you can't write generic code in terms like "I take F and throw all E that F throws plus X".
> I like to think of monads as wrappers around data
Technically they're a wrapper around a data type. Maybe Foo is a Foo that might not be there, [Foo] is zero or more Foos, etc. Which is actually describing a Functor, but Monads are also Functors, the "monadness" comes from the particular functions like `bind` (or `flatMap`) that work on them.
Okay hear me out:
I don't like exception handling, because it doesn't feel "pure" and consistent with the rest of my program flow. If any errors happen, I want to know what they are so I can plan ahead. You might say "well, just catch your exceptions dammit". But see, what if I forget to catch an exception right away and it propagates? Also, other than Swift, I don't know of any programming language that let's me explicitly mention if a function could throw an exception. In Python, for example, you can't just do Optional[str], because returning None is different from raising an exception. So by just looking at a function type signature, one can't know if it throws, which means you never know if you should use try/catch or not.
But let's say you take care of all edge cases and return None in those bad paths instead of raising exceptions. Let's also assume that Python does't suck and it never raises exceptions by its own standard libraries (e.g., json throws). The problem is: How do we know if the function returned None because the evaluation was None, or because it failed at some point?
So a better approach is to explicitly say the function returns "something" which may or may not be there. If it's there, then the function worked correctly, even if the value is None. If it's not there, then the function failed. Go does this. Rust does this better. Haskell has had this since ages thanks to Monads.
I like to think of monads as wrappers around data. In Python, I simply write a Result monad (similar to Rust). In Haskell, this is called a Maybe monad.