I agree, mainly because they are distracting from the article content.
It reminds me of boring presentations at work where they shove in a star wars pic / reference. Everyone laughs then its back to the boring topic and everyone falls asleep again.
I hate to say it, but if it increases engagement with the article, then it won't stop. The vocal minority will always be outclassed by the majority because these sites are optimized for the majority. It would be cool if the site adapted to the current user, rather than to the average user. Presumably, that would increase engagement even more, but probably not by much, since the majority is already happy.
I suspect it's used ironically. "A capacitor, a very complex object coming from the Classic Electrodynamics Theory" would work similarly when preceding a description of two parallel metal plates.
I think this article makes a simple concept seem unnecessarily convoluted. The first paragraph says:
> This very complex object coming from the Category Theory is so important in functional programming that is very hard to program without it in this kind of paradigm.
May I try to dispel this myth that monads (and functional programming ideas in general) are complex?
New functional programmer are often faced with a dilemma: how do I do side effects (e.g., mutable state) in a purely functional way? Isn't that a contradiction? In 1989, Eugenio Moggi gave us a compelling answer to these questions: monads. The idea of monads originally comes from category theory, but category theory is not at all necessary to understand them as they apply to programming.
We start with this: since a pure functional programming language doesn't have step-by-step procedures built into the language, we instead have to choose our own suitable representation for impure programs and define a notion for composing them together. For example, a stateful program (a program that has read/write access to some piece of mutable state) can be represented by a function which takes the initial state and returns a value and the new state (as a pair).
So, for example, if the mutable state is some integer value, then stateful programs that return a value of type `a` will have this type:
Int -> (a, Int)
Let's give that type constructor a convenient name:
StatefulProgram(a) = Int -> (a, Int)
One key piece of the story is that this type constructor is a functor, which is just a fancy way to say that we can `map` over it (like we can for lists):
Then, if we have a stateful program which produces a string (i.e., its type is `StatefulProgram(String)`) and a function `stringLength` which takes a string and returns its length, we could easily convert the program into a new program that returns the length of the string instead of the string itself:
newProgram = map(program, stringLength)
Another way to phrase this is: we can compose a stateful program with a pure function to get a new stateful program. But that's not quite enough to write useful programs. We need to be able to compose two stateful programs together. There are a few equivalent ways to define this. In Haskell, we would have a function pronounced bind that takes a stateful program and a callback. The callback gets the result of the stateful program and returns a new stateful program to run next.
Some languages call this function `flatMap` instead of `bind`. In JavaScript, this is like the `then` function for promises. Whatever we call it, we can easily use it to write a helper function which sequences two stateful programs:
This amounts to running the first program, throwing away its result (see that the `result` variable is never used), and then running the second program.
One more ingredient is needed to make this `StatefulProgram` idea really useful. We need a way to construct a stateful program that just produces a value without touching the state. We'll call this function `pure`:
a) First of all, it needs to be a functor. That amounts to having a `map` function like we defined above.
b) We need a way to construct a `StatefulProgram(a)` given an `a`. That's our `pure` function.
c) We need some notion of composition. That's given by our `bind` function. (And note that `sequence` is just a special case of `bind` where the callback doesn't use its argument.)
Category theory also gives us some common sense laws that monads must satisfy. For example, `bind(pure(x), callback) = callback(x)`.
The brilliant insight of Eugenio Moggi is that these three functions are essentially an interface for any kind of side effect. Mutable state is just one example. We could represent other kinds of side-effectful programs in the same way. For example, a program which returns multiple times could be represented as a list. Then the `map` and `flatMap`/`bind` functions are exactly what you expect, and the `pure` function just constructs a list with a single element. Other examples of monads are IO (for interacting with the operating system), continuations (for doing fancy control flow), maybe/optional (for programs that may return a `null` value), exceptions, logging, reading from an environment (e.g., for threading environment variables through your program), etc. They all have the same interface, which is represented in Haskell as the `Monad` type class (type classes are Haskell's notion of interfaces).
Haskell also provides a convenient syntax called `do notation` for working with monads (this is a vast generalization of the async/await syntax that is creeping into some popular languages). For example, a stateful program that reads the state and mutates it could be written like this:
program = do
x <- get -- Read the state
put 3 -- Update the state
pure (x + 3) -- Return what the state used to be plus 3
In our syntax, that would be equivalent to writing:
I have read a large number of articles that tried to explain these concepts but they never stuck. This comment just blew them all away. The concept seems almost obvious now haha
Chaining is as old as hills and can be used to avoid callback hells. What monads are really is a type respecting chainable object with 3 methods: value, map and then.
Which is awfully similar to Haskell's Maybe. Ignoring boilerplate and comments, the entire module is just [0]:
data Maybe a = Nothing | Just a
deriving ( Eq, Ord )
And the deriving line isn't really nessasary.
The Monad module is larger and contains code specific to Maybe [1]:
instance Monad Maybe where
(Just x) >>= k = k x
Nothing >>= _ = Nothing
(>>) = (*>)
fail _ = Nothing
(Only the first 2 lines are needed. The 3rd is an optimization, the 4th is a historical wart that will be removed in future versions (moved to MonadFail)
Maybe is an algebraic Option type. It just happens to implement an interface that abstracts away the if(None) checks that you would be doing anyway. Lazyness has nothing to do with it.
The option monad is literally isomorphic to maybe. It 100% follows the definition of a monad and is an instance of the monad type class in Cats and Scalaz.
Many algebraic data types are monads. I’m not sure why you think that is a contradiction.
I think you're overthinking it. The isomorphism simply exists. And you don't wrap things into a monad, you just write an instance for it -- it has no effect on the data type itself, it simply allows to use the data type in monadic contexts.
That post is misguided for so many reasons. In order to say something 'forms a monad', you need to define over what operations. That poster assumes Promise forms a monad using regular javascript function composition as the function composition operator. This is stated without justification, and there are other notions of javascript function composition which would preserve the functor composition law.
More clearly, Promise indeed forms a functor, if the domain is the set of Javascript functions which do not return 'Promise<X>'.
That means they do meaningless unnecessary wrapping too. It is even more ridiculous for a weak-typed language.
The only point of a Monad is that in a lazy language it desugars into an implicit nested functions, which guarantees the order of evaluation.
The monadic code, then, could be generalized over >>=, >> and return, and given that, specialized instances of a Monad class could be made, like Maybe, ST and what not.
The only meaning in performing these steps is to establish a guaranteed order of evaluation (via implicit nesting of functions) for serialized abstractions.
But that is ok, most of Javascript coders never studied CS fundamentals or comparative PL.
So you think there's a construct that allows you to do what Promises do that are simpler and do not form a monad?
Please, do tell.
> But that is ok, most of Javascript coders never studied CS fundamentals or comparative PL.
Can you explain what you mean by this? You seem to be under the impression that javascript the language did something extra to make Promises form a monad when in reality, the very idea of a Promise forms a monad to begin with, independent of any particular implementation in javascript.
I think you will find it productive to consider that you may be wrong. Your examples are literally monads (and I use the word "literally" literally). I think the problem you are facing is that your understand of what a monad is may be incorrect (and this, in turn, may be the fault of some random bad tutorial on monads).
When you are discussing things of this fashion it can be helpful to allow yourself some room to accept a view which is different than you currently hold. Otherwise it can devolve into a shouting match over who is stupider, rather than an opportunity to see things in a different way (and perhaps learn something interesting). When 2 people are arguing, the one who is right wins the argument, but the one who is wrong has the most to gain because they can learn something. By insisting on being right, you forever cut yourself off from that potential.
I know you didn't ask for that advice, so if it's not appropriate for you, please feel free to file it in the round bin.
May be. I think a Monad is an abstraction which generalizes a transition, similar to a step of logical deduction.
It Haskell a similar concept is actually used to compose what they call actions, originally used to implement IO. Out of this particular design choice, there are some specialized instances of Monads in Haskell, including State Monad.
In the context of a lazy language, like Haskell, where the order of evaluation is not defined and each expression is an implicit thunk, monadic composition is necessary and sufficient to implement serialized sequenced actions, defined as different instances of Monad type-class and to ensure, by the type checker, that these actions are of proper type (isolated from any other instances) and are properly serialized. Just this.
As a consequence of being desugared into function composition with parameter passing (>>=), which is only relevant for a lazy language, there is literally no way to access the state from outside of each pair of nested functions, so things like ST or STM are implemented as Monad.
All this is relevant for Haskell and irrelevant in Scala, where you don't have to compose functions to ensure order of evaluation. Algebraic data types and "semicolons" is good-enough.
A monad is a descriptive term for certain objects with have certain properties. For example, a ring in algebraic theory is a set together with two operations (+ and ) with certain properties. The natural numbers (1,2,3,...) together with + being addition and being multiplication form a ring, but to say that 'the natural numbers were constructed as a ring' or that 'a ring is an abstraction for the natural numbers' is wrong. Both rings and natural numbers are descriptions of things which happen to fit.
Similarly, category theory monads are a description of things with certain properties. These happen to be satisfied by IO actions in every language and Promises in JavaScript. Haskell allows you to define this abstraction within the language, but it is not the only language that allows this.
I don’t think your thinking about ordering is correct, in fact I remember reading a blog post somewhere (can’t seem to find it now) that made these same claims and was really confusing/poorly written, I’m wondering if you also read it. Anyway, monads can be about ordering but aren’t necessarily always used for this purpose, an example of a monad that doesn’t order operations is the Reader monad. I generally think about monads as generalizing composition, and not necessarily “transitions” because there are some contexts (ie Reader) where “transition” doesn’t really fit what’s happening in the computation.
> I think a Monad is an abstraction which generalizes a transition, similar to a step of logical deduction.
I think this is where we're going off the rails. A monad is a specialisation of a functor (the infamous "A monad is monoid in the class of endofunctors", which I'll explain as briefly as I can later).
A functor is simply a mapping from one set to another (possibly the same set). So if I have a function that maps integers to characters, for instance this whole setup (the set of integers, the set of characters and the function that maps them from one to the other) is a functor. It gets a bit complicated when we translate that to a programming language. The set of all integers is a type. The set of all characters is a type. So we can describe the mapping from one to the other as a functor. You will probably just recognise that as a function -- and, indeed, a function is a specialisation of a functor (although the general case of a function is quite complex and I don't have anywhere enough space to explain it).
What is important to understand, though, is that the thing we are trying to map is important. We need somewhere to hold that value so that we can map it from one type to another type. Any container will do (a list, array, tuple, or even a partially applied function). When we are talking about programming with functors, we implement them using that container and a function that transforms the content from one type to another (possibly the same type).
When we map the value from one type to another, we want to put the value in a functor as well (going back to the original concept of mapping from one set to another, after we've mapped it, it is still a functor -- just one potentially holding a different typed value). Normally we implement a function called "map" (or "fmap", for "functor map") on a functor that accepts a transforming function that maps a value from one type to another. "map" then puts the result back into a functor (which is just the same kind of container you started with). As I can see you are familiar with FP languages, I'm sure you are familiar with this implementation. You have an array of integers, you can call "map" with a function that maps integers to characters and the result is an array of characters. Both the array of integers and the array of characters are functors.
Again, I'll reiterate this because it will be super important later: any container is a functor if you can map it's contents. In fact, anything that can hold state (like a partially applied function) is a functor if you can map its contents.
A monad is a specialisation of a functor. Let's go back to "A monad is a monoid in the category of endofunctors". I like that quote because it sounds like nonsense, but it's really very easy to understand if you know the definitions of the words.
A "monoid" is another specialisation of a functor. It's a functor for which there is a binary operator and an identity element. For example, let's take integers. They are monoids under addition. If we take 2 integers and add them together. There is a particular integer, "0" for which when you add anything to "0" you get the same result again. We can use addition in our mapping function (usually one of the parameters being partially applied) and if we pass it "0" we'll get the same value back.
Monoids seem a bit pointless, but they make a lot more sense when you are thinking about folding. A container isn't foldable unless is is a monoid (it's a great kata to do, if you are interested). There are lots of other implications, so I highly recommend implementing the whole hierarchy of functors from scratch to see why things are as they are.
Getting on to monads, we now know what a monoid is (not too difficult to understand). We only need to know what an "endofuctor" is. This one is also really easy to understand. An endofunctor is a functor for which the result of "map" results in exactly the same type as the original. So if you had an array of integers to start with, you end up with an array of integers. That's all it is.
"A monad is a monoid in the category of endofunctors". The last bit we have to explain is what a category is. It's just a grouping of mappings. I could have a mapping that adds 5 to an integer. I could have a mapping that adds 6 I could have one that subtracts 1. Each one of those mappings gives a result in the set of integers. As a grouping of mappings (i.e. "category"), all of these mappings describe an "endofunctor".
Finally we get to the result. In very plain terms, a monad is just a container (or anything that can hold state) which has a function that modifies the contents such that the result is guaranteed to have exactly the same type. That's all it is.
It sounds disappointingly plain, but it has surprising power. For one thing a monad holds state. Obviously this is important. But if you use the monadic mapping function (usually called "bind"), then you are guaranteed to be returned a monad of the same type. This allows you to freely chain operations without fear that something will break. This is what a monad is for.
Where I think you are getting confused is that you have seen advanced applications of monads and you have come under the mistaken impression that these are the reasons monads exist. Indeed, monads are required in a pure functional language to do those things. The nice thing about a monad is that it can hold any state for which there is an identity element. The implementation of the container is completely beside the point. As I said, you can partially apply a function and that is potentially a monad.
The even more interesting thing is that any abstract datatype can also be a monad. Where you are saying, "We don't need monads for this, we only need abstract data types", it's a bit nonsensical. These ADTs will be monads if they are used in that fashion. Indeed, objects in OOP are often monads! They just need a transforming function that returns the same type of object.
Yes, it was really good read, thank you. I hope I could write as clearly.
Look, however, at the use of "is" in my comment and yours. You are applying "is" to some abstract, nonexistent categories, categorizing abstractions and naming the resulting categories. I use "is" to describe what is going on, particularly in Haskell and Scala.
The whole abstract hierarchy I regard no more meaningful as hierarchy of chakras in crya yoga texts or similar sectarian pseudo-logical but disconnected from reality teachings.
While I really appreciate that you have mastered this abstract hierarchy and could in clear and precise language explain it (which is what a mathematician supposed to do)
I personally refuse to take statements like "any abstract datatype is a monad" seriously.
It is a plain type-error. You are trying to say that a triangle is a number. The correct and precise way is to say "could be viewed as", because you are literally superimpose one abstraction upon another, or view one through another, if you wish.
All these nested generalizations could be superimposed on programming, but it is fundamentally wrong to say that something in programming "is" one of these abstractions.
You are speaking like a mathematician, I am speaking like a non- (or rather anti-) Plato-Hegelian philosopher.
Hmmm... let me put it another way... When people are talking about monads, they are talking about what I was describing. :-) They use monads for what you are describing, but they also use them for many other things. The disconnect which created the conflict in the conversation was that you were using the term in a non-standard way. A person can use a word to mean anything they want, but they can't reasonably complain when other people don't understand what they are talking about.
Again, in hopes that it will be helpful to you (though I realise I'm really pushing my luck) when I discussed the issue about needing to be right, I'm referring to this kind of reply. I will try to be as modest as I can by saying that "it takes one to know one" ;-) And even my reply here is probably one bridge too far. I should probably simply understand that you are fixed to your point of view and leave it at that.
However, my goal in responding to your comments was to help you see the disconnect and to help you understand why people found your replies combative. There are definitely ways to portray your arguments such that you are in the right, but these strategies will not help you communicate with others. Western styles of communication often praise debate and the winning of arguments, but gloss over the loss of value when we look past each other.
Anyway, like I said, I've almost certainly gone too far and I don't wish to antagonise you further. You are clearly a very smart person and I think you will eventually figure it out with or without my help. If anything I've said helps you get there faster, then I'll be extremely happy. If not, then I'll apologise for any stress I may have added.
Edit: I would be remiss if I didn't point out that there are some ADTs which can not be monads. The exploration of that is extremely interesting. Since you clearly know Haskell, I recommend http://mightybyte.github.io/monad-challenges/ In challenge 1, the initial implementation of the Rand type can not be a monad. Why not? It's really interesting.
This exchange has nothing to do with social justice and the people who are disagreeing with you aren’t snowflakes. If we define snowflake as someone who reacts to something they disagree with with outrage and accusations, then it’s clear who is being a snowflake.
Back to the technical topic, the point I and others are making is that yes, you are correct that Option/Maybe is “just” an algebraic data type that is the sum of a type and a terminal object called unit, None, or Nothing. However people are disagreeing with you that it’s not a monad. It mathematical must be a monad, with the same certainty that it’s an algebraic data type or defines an endofunctor from A to A + 1. You can debate the usefulness of this fact, but your comments suggest you think it is “just” an algebraic data type and it somehow doesn’t induce a function that obeys the algebraic monad laws.
Also to follow up on your link to the documentation of promises in Scala, here is a quote:
“To simplify the use of callbacks both syntactically and conceptually, Scala provides combinators such as flatMap, foreach, and filter used to compose futures in a non-blocking way.”
‘flatMap’ is exactly the bind function for the promise monad. It doesn’t have to explicitly implement some monad interface to be a monad, it just is.
To be a Monad you have to implement at least 2 functions >>=, and return, which must follow so called Monadic laws (of proper composition - associativity, etc). flatMap is just a function.
One more time - Futures are orthogonal to Monads. They may be viewed as such, but it is not necessary. Having only flatMap is sufficient for a strict language.
I was assuming a pure core of Scala, its true I should have been more explicit. Arbitrary side effects and IO make it impossible to reason algebraically/equationally about your programs.
> The only point of a Monad is that in a lazy language it desugars into an implicit nested functions, which guarantees the order of evaluation.
That is not true at all. You seem to confuse side-effect-free with lazy and the state monad with any monad. Monad instances like options or either data types allow for the simple composition of functions that yield them. In any language. Promises form a useful monad because there is no other way to talk about values that have not been evaluated yet.
It's really funny to see someone complaining about the lack of CS fundamentals in other people but does not know the difference between a weakly typed language and a dynamic one. :-D
To recapitulate some basics: JS is a strongly typed dynamic language.
In both cases that's clearly implicit type coercion at runtime.
So according to the above constraint there would hardly be any strongly typed languages… (But quite the opposite is true in fact).
Weakly typed means you can hose the type-system somehow and then perform some "illegal" or completely nonsensical operation. For example that's easy in C, but not possible in JS. In a strongly typed language any value has some precise type at any time. This is true for JS so it's strongly typed.
Functional programming was invented by Backus to solve the problem of interchangeable parts. Unfortunately, he did not finish his work, and as a result people are still trying to paper over the missing hole in the theory. State monads as described above make the process of storing and manipulating state a very obscure and mysterious process. This additional complexity and abstraction can only add to the lifecycle cost of software generated using this technique, with an attendant increase in maintenance cost. If you were to take a 10 year old, and give them basic training, would they be able to understand this? I doubt it, yet computers at their core are incredibly simple. So a better paradigm awaits.
The state monad, in spite of the scary name, is an incredibly simple idea, being about functions with this signature:
S => (A, S)
That’s it. Of course this doesn’t include the definition of the monad instance, however those operations come very naturally from trying to work with, to manipulate these functions. In fact many people reinvent the state monad.
Furthermore this does a great job of simplifying impure functions that manipulate state, because for those functions the state evolution does not appear in the signature. Or in other words the state is still there, as an implicit input and output to your function, but the type system can no longer protect you and the function's output will be dependent on the history of all previous function calls. That's anything but simple.
And yes, I believe you can explain it to 10 year olds. If the 10 year old can understand functions, he can understand this as well. Your grandma might have a problem with it though.
You are returning new state of the world with every call that modified it and using monad guarantees your calls are sequential. Where is the obscurity?