EDIT: I have serious problems with the post, because a) it claims that discovering one application of monads is understanding monads, b) for me the true strength of monads shines in strictly typed languages, everything else is just an approximation of the concept.
Say you're working with some data structure that contains/emits numbers: a pointer to a resource containing a number. A list of numbers. A function that returns a number. An optional number (or null).
A common operation is unpacking that structure to get a number, applying a function to the number, and packing it back up: Reading from the pointer, applying the function, and returning a pointer of the result. Applying the function to each element of the list and returning a list of the results. Composing a function with another function. Applying a function to the optional number or just returning the null.
When you're writing code on this, it's error prone to do the unpacking, application, and repacking. It's much simpler if you can write code that looks like `def f(x): return exp(x)/x + 23`. Much more testable too. If you have two or more of these structured things, it might get even more error prone. It's much easier to write code that takes three integers and does stuff, instead of writing code that takes three pointers/lists/functions/optionals.
Monads are part of a hierarchy that abstracts that. Anything that defines that sort of function application in a particularly convenient way is a monad. There's more to it, but that's why it's useful.
It lets you write code dealing with the things in your data structure, letting you mostly ignore the structure itself.
-------
In this specific situation, say you want to replace your error handling with something else. Maybe it writes to a log file then errors. Or maybe it does something fancier. Or maybe you even change the way you get the resource as well as the erroring to something fancy. As you swap out the "structure" code, with a monad it's just switching to a new monad, rather than refactoring the business logic related code. It's a nice separation of concerns.
Aaand with this statement you skipped over what IMO is the most important missing piece, because everything above it fits higher-order functions such as map(), which as far as I understand aren't monads.
tl;dr map works for applying the simplest functions to "containers." To apply more interesting functions, you need bind, pure/return, and/or whatever applicative's <*> is called.
Let's say you're working in something like Maybe. You might want to write some code like
f :: Float -> Float
f x = 5 + x
In that case map is fine. fmap lets you focus on simple code like that. Or maybe you want to write f :: Float -> Maybe Float
f x = if x == 0
then None
else 5 / x
In that case you need more than map, because you don't want to deal with what map would give you: a Maybe (Maybe Float). (>>=) lets you still focus on simple code like this, since you dont have to deal with any unpacking/flattening, which was what I'm saying is why monads are so useful.More importantly, you need more of the FAM hierarchy than map if you care about multivariate functions, which I'd say is most code. Lets say we're working with
f :: Float -> Float -> Float
f x y = x + y
We want to write code like that and use some version of function application (like map). If we just use map, we get the following f <$> maybeX :: Maybe (Float -> Float)
which isn't at all what we want, because we can't apply it to a maybeY (or even to a float y, which `pure`/`return` lets us treat as a maybeY). If we define an additional way to apply that Maybe (Float -> Float) to a Maybe Float, we've defined Applicative, forcing us to go beyond a functor.The motivation and usefulness is the same throughout: we just want to write code and apply functions that don't care about the structures emitting/containing our inputs and outputs. It just turns out that there are three cases depending on the kinds of functions we're writing and applying
f :: a -> b -- functor is sufficient, like you say.
f :: a -> b -> c -- functor isn't sufficient. applicative is.
f :: a -> m b -- functor and applicative aren't sufficient. monad is.
I wrote a series of posts ages ago deriving all these from that one motivation (in a more fleshed out manner) http://imh.github.io/2016/05/26/why-monads.htmlNope. I think this is why you don't see where the previous post falls short, too much familiarity so you don't realize you're skipping over important aspects.
> so I'm going to cross my fingers that you know it in haskell and use that notation and terminology to save us time.
I get just enough Haskell to understand the first 3 code blocks, and can guess what the 4th is depicting, but am not sure.
Few of them are actually bound to syntactic sugar known as "do-notation", that let's you write that sequenced code in post as if you were binding simple variables, adding branching, effectful statements or auxiliary definitions along the way. This really pays out when you start turning simple monads into so-called "monad transformers", that let you stack multiple behaviours/wrappers on top of each other, keeping the same pretty do-notation untouched.
A lot of times that's a perfectly fine decision, but six months down the road when the code has grown a lot, you might find an alternative way to get the resource on a fail
If you made a decision to throw an exception immediately after failing to get the resource, you then have to either rewrite the logic, which can be very expensive, or catch the error, which bloats the code (throwing the exception is now redundant and is fixed by adding code that catches that exception)
By instead putting the return value in an appropriate monad, you can postpone throwing the exception until you're sure that there's no way to recover
Throwing an exception is still something that's necessary occasionally, but it should not be done until there's no possible way to recover, and be done in a way, so it's easy to rewrite if a way to recover becomes available at some future point in time
Notice that that short introductory article already has examples of two different monads. People use many more of them.
So the ability to define this independently isn’t useful. You want to create a standard. Whether that goes into the standard library, or gets a little language syntax support as well, is something you can argue about, but is pretty arbitrary and probably just comes down to the style of the language.
The burden on the developer is equal: the hard part is learning the conceptual patterns, how to compose solutions in terms of them, and how other libraries you use expect you to use them.
Monads aren't about handling error. That's just one of two examples on the article (and yes, that one is on the standard library) and it's used for more stuff than errors already.
> You want to create a standard.
Monads are a standard. That's basically all they have into them. If you do it right, most of your monadic code won't even know what monad it's running in.
Not really. That's maybe the impression you might, but it's not true. The "language syntax support" isn't really syntax, it is a deep conceptual change of the language. Now instead having functions return one value, they _can always return 2 different types_ and you don't know if they really do unless you know the implementation (which sometimes you can't).
That not only causes pain on a daily basis for most developers, it especially causes pain for library authors (which you are maybe not, so it is not as visible to you) and it especially causes troubles down the road with other concepts.
As example, check out Java's try-with-resources. It is a bandage over a bandage and while it improves things it is difficult to get right and has a lot of corner cases and really strange behaviour, especially in combination with constructors.
With plain simple language features, something like that does not happen.
But there are many other examples that are useful in practice (IO, streams, parsers, lists, operations in context, futures, etc as mentioned in other threads). A monad is the interface you need to implement for each of these to compose nicely.
Then one day you'll need to compose Foo's, so you'll ask yourself "is there a monad for Foo's?" and if there is the code generally writes itself.
try:
user = getUser()
profile = getProfile(user)
pic = getProfilePicture(profile)
thumb = getThumbnail(pic)
return thumb
except Missing:
return None