179 comments

[ 3.6 ms ] story [ 195 ms ] thread
The more I've learned about Haskell, the more I've come to realize that Haskell's approach to IO really has very little to do with Monads. Fundamentally, Haskell implementations sequence IO actions using hidden magic. (GHC uses dummy world variables.) As it happens, the operations for constructing IO actions from pure values and for sequencing IO actions form a Monad. But that's not really any more interesting than the fact that e.g. (\x -> [x]) and concatMap form a Monad.
(comment deleted)
Reading this: https://wiki.haskell.org/IO_inside did not make me come to that conclusion.

The IO monad is noncommutative, and thus controls sequencing of operations, because of data dependencies, more or less hidden.

A "Haskell wizard" tried to tell me the ordering of operations was implemented through low-level magic a few weeks ago. The fact is that whatever ultimate form GHC might compile your code to, the sequencing is expressible in the semantics of Haskell code. Getting sequencing into the semantics is one of the problems which monads solve.

It's worth noting that the IO monad implementation is very much compiler magic and can't be done in library level haskell. Evaluation order without IO/ST is undefined in Haskell. This is similar to write barriers, you need a magic primop to specify external data dependencies.

Without that magic the compiler may inline everything, drop the 'uselss' state token and happily move your buffer freeing before the allocation - or drop it entirely.

Alternatively you could mark every function as noinline and end up with fantastically slow code.

Data dependencies don't viably smuggle operation sequencing into Haskell. Consider e.g.:

   fma a b c = (a * b) + c
The sum depends on the product but this doesn't sequence anything: it may be compiled into a single fma machine instruction. More generally inlining, CSE, etc. can break apparent data dependencies.

It's simpler and cleaner to merely state that the Haskell runtime is written to execute IO actions in sequence.

The data dependencies in how monads desugar does force the evaluation order, because the result of the first one is passed to the following one and so on and so forth. The desugaring turns each monadic action into a lambda with one argument.
I think that millstone (https://news.ycombinator.com/item?id=17646166 )'s point was that the sense in which desugaring of monads forces evaluation order is the same as the sense in which

    fma a b c = (a * b) + c
forces evaluation order: you need the result of `a * b` in `fma` in order to add `c` to it. One can argue whether both of these, or neither of these, have the evaluation order forced, but I don't think that it is reasonable to argue that one does and the other doesn't. (There is a slight artefact from the fact that addition is commutative, so that it makes no difference whether you compute `c` first and then add `a * b` to it or vice versa, but I think the idea should remain.)
To discuss this properly, you need to consider the evaluation strategy, which is part of the semantics of the language. Strict, lazy, call-by-need: things do get evaluated in a prescribed order, and sequencing using monads (predictably) involves strictness, meaning that a function argument is evaluated before the function.

These details of evaluation order are important to know, and they are not something to be argued about: they are well-specified and the compiler doesn't just casually make lazy code into strict code or vice versa. That would cause chaos. And what instruction set Haskell is being compiled to is totally irrelevant to this level of the language's semantics.

https://en.wikipedia.org/wiki/Evaluation_strategy

> To discuss this properly, you need to consider the evaluation strategy, which is part of the semantics of the language.

Yes, you are certainly right. I misread and thought incorrectly that the discussion was about whether certain constructs forced data dependencies in any theoretical language, not in the particular language Haskell.

> These details of evaluation order are important to know, and they are not something to be argued about: they are well-specified ….

While I take your meaning, I think that you don't mean literally what you said: they got to be well specified precisely by being argued about, and will hopefully get to be even better specified by continuing argument. They are certainly not for me to argue about, though.

>sequencing using monads (predictably) involves strictness,

Sequencing using the IO monad predictably involves strictness. There is nothing in the type of >>= that forces strictness. That is why, for example, you can usefully bind infinite lists in the list monad. So in e.g. [1..] >>= return . (+ 1), the list is not evaluated before the call to (+).

In fact, binding is not strict in the IO monad either (that's why lazy IO works). It's only the special magic world parameter that is, in effect, evaluated strictly.

Yes, monads don't imply strictness.

The idea of using a data dependency to force a certain order of evaluation predates monads and it predates laziness. So it is implemented accordingly, and any hoops that need to be jumped through in the implementation to make it work aren't magic—they are just necessary to express a long-established pattern.

What monads offer is a way of discreetly passing the state from one operation to the next (the State monad is the model for the IO monad). It removes the need for the programmer to think about that aspect of the plumbing, without creating magic elements in the language.

http://okmij.org/ftp/Computation/IO-monad-history.html

>So it is implemented accordingly, and any hoops that need to be jumped through in the implementation to make it work aren't magic—they are just necessary to express a long-established pattern.

The hoops are magic in the sense that they can't be expressed in Haskell 98 or Haskell 2010. A Haskell compiler is perfectly entitled to optimize out any dummy values which aren't used. That includes dummy world variables.

More generally, it simply isn't possible to use data dependencies -- even real data dependencies -- to control the order of side effects. The Haskell standards just don't provide the guarantees necessary to make that workable, for the reasons that millstone gave.

(comment deleted)
Yes, but there is no guarantee that the argument passed to that lambda will ever be evaluated. Consider e.g. the following expression in the identity monad:

do { x <- return [1..]; return 7 }

That evaluates to 7, and the infinite list is never evaluated. So the 'data dependency' that monadic bind gives you doesn't, in general, force evaluation order. It's only because the implementation details of the IO monad ensure that the dummy world parameter is always 'evaluated' that you get guarantees about evaluation order. Note that the dummy world parameter is just that -- a dummy -- and no computation actually depends on its value. It's pure compiler magic.

I’m not sure I follow, since the implementation details of any given monad have very little to do with monads. It’s the fact that IO/Continuations/Etc can be just another monad that was interesting when it was discovered. It’s cleaner to have 1 pattern/framework for N domains than to have N for N.
>since the implementation details of any given monad have very little to do with monads

Well, yes, that's the point. You don't need to understand anything about monads to understand how the implementation of >>= for IO forces the correct order of side effects.

Actually, that is not entirely true. The monad bind operator introduces a data dependency which is responsible for the sequencing itself, not the actual implementation of the bind (i.e. it is a result of the type of the bind).

But I do agree with the general idea that Haskell as a language is still a leaky abstraction, you still need to understand a lot of behind-the-scenes stuff, like how the RTS interacts with the OS ABI, how concurrency is implemented, the consequences of laziness and so on. Just knowing the denotational semantics of the language is not enough.

I suppose that knowing only denotational semantics would be sufficient only for programs consisting of pure functions. E.g. a batch job that receives input parameters and returns the result via the minimal runtime-provided means would be an example.
>The monad bind operator introduces a data dependency which is responsible for the sequencing itself, not the actual implementation of the bind (i.e. it is a result of the type of the bind)

No, there is nothing in the type of >>= which guarantees sequencing of side effects. So for example:

a >>= (\b -> c)

There is nothing in the type of >>= that guarantees that a will be completely evaluated before c is evaluated.

I suspect this misconception is what makes many people think that Haskell's way of handling IO has something crucially to do with monads.

Ordering of operations is done by the non-associativity and non-commutativity of the bind operation. Data dependency is not sufficient for ordering IO¹².

Well, that's in theory at least. In GHC Haskell the ordering is not denoted by neither data dependency ot the operator semantics, but by a complex set of rules that change from one version of the compiler to the other.

1 - Although it is sufficient for any monad that lacks real-world effects.

2 - Well, technically nothing is enough for really ordering IO operations, but those two properties give single-threaded semantics to IO.

It's still useful to understand it a bit deeper to see the similarity between e.g. lists, optionals, and IO.
Well, YMMV, but I don't have any trouble understanding the concept of one thing happening after another without reference to all that other stuff.
I find that kind of comment incredibly misleading. The concepts that most people attach to lists and optionals don't touch functor semantics and thus are miles away from monads.

Saying that lists are an instance of Monad in Haskell says much more about novel ways you can use a list in Haskell than about properties of a monad.

Still, questions like "how do I flatten a list of lists" are common on stackoverflow.
I guess it looked to me like you were singling out IO as a special case, when what you are saying is true of all abstractions. You can just as easily say "you don't have to understand algebraic groups to do integer addition."
As far as I can see, explaining IO in Haskell in terms of monads is unhelpful. It would be like using monads to explain how list concatenation works. The fact that e.g (\x -> [x]) and concatMap form a monad is not really very helpful in understanding what concatMap does.
You're describing my pet peeve with research in programming languages, or more generally use of abstractions in CS. What you're writing makes sense: Understand the structure of your problem, then think of similar problems you've seen before, finally try to abstract away details that are irrelevant for the solutions in each case. If you're lucky, all of these problems are an instance of one general one, and need to be solved only once. In your example, it's that IO and [] are both monads and thus share some structure.

What's often happening in PL theory, though, is that people start off with axioms/syntax and then mutilate the actual problem until it fits the syntax (still badly). That's how you get ridiculous stuff like "everything is an object" or "everything is a file". In PL theory, people will usually first make up some syntax/theory and then search for models. If physicists worked the same way, they'd write down random formulae and would then go out to find phenomena described by them. You'd probably read this comment on a cave wall.

Axiomatic formulations of a problem do not have the same goals as abstractions. They can appear similar on the surface, but one has to do with the laying of a foundation and the other is about problem simplification. One is about allowing great leaps in understanding after a great deal of derivation, the other about the reduction of work via problem equivalence. Criticizing someone for using an axiomatic formulation rather than a proper abstraction in programming language theory is like criticizing an architect for using pencil in laying his foundation on paper rather the much more appropriate cement at the construction site.

You don't get everything is an X because of axiomatic formulation. You get it for the same sort of reason that instead of spending ages describing the configurations of atoms in front of you, you throw all that out and call it a screen. Is everything still atoms? Yes! But we can call it a screen and that makes things massively easier to reason about and so we do it, because we like to reason well. But there are still benefits to thinking the other way, thinking from the very foundations. They are just different benefits, which is why we choose to think of problems from more than one perspective.

I didn't mean to critize all axiomatization but the concrete axiomatizations arrived at by PL research. In my opinion, one should start with a thorough understanding of the problem domain, and then try to come up with a syntax that allows expression of your intuition as faithfully as possible. Form follows function. Instead, I feel like PL research is often too focused on the concrete syntax considered, thus function follows form. Case in point: Building models for the syntax at hand is often an afterthought, done only to prove consistency. I'd argue that one should start with as precise an understanding of the intended models as possible, and then come with up with an axiomatization (thus, syntax) for the intended semantics.

For example, the notion of elementary topos has been invented because its creators wanted to capture the way Grothendieck toposes kind of behave like constructive sets. This I find very useful, and also the axiomatizations of elementary toposes. On the other hand, Martin-Löf type theory didn't have a formal semantics at first, then an erroneous one, and finally ~20 years later a kind of acceptable one. And its category of models is... not really interesting. Except for categories of assemblies, I don't know of a single model of ML type theory that's not also an elementary topos. And the interesting thing about assemblies is that they can be turned into objects of the associated topos... so yeah.

And yet MLTT led to Coq, Agda, Idris and Lean, while your ”PL practice” approach sounds like it would lead to, well, Go.
>If physicists worked the same way, they'd write down random formulae and would then go out to find phenomena described by them.

So, String Theory?

Kind of, from what I (complete physics noob) hear.
Unsurprisingly, Physics get to decide what abstractions they use the same way developers decide what languages to create. They invent some notation, start describing everything they know on that notation, and if it's productive push it further and further until it breaks.
The "everything is an X" is what I call "foundationalism". It is a disease that started with Turing and Church, except that when they started it it was not a disease. But ever since people forgot that Y and "an encoding of Y as X" is not exactly the same thing. For some purposes it might be, but not for all purposes.
This, I feel, is the case with all monads. Even "free monads" are just an interesting name for an AST with embedded host-language functions.

Monad is interesting in retrospect. After you "get" X, Y, and Z you come to realize that they're all monads and then maybe you get some mileage out of the mental compression.

This is a crucial point. There’s almost never value in using any abstraction just once; the value comes when you learn to recognise it and apply previously learned lessons in new situations.
> The incomprehension of the newbies is matched in intensity only by the smugness of the experts.

Oh good. A write up that’s aware of how unapproachable monads seem. Maybe this will actually be a good, plain-English breakdown.

Four paragraphs later:

> Monads, as a mathematical concept, can be always used as a part of the semantic model, but whether they transpire into the type system of the object language is a matter of design choice.

Oh... well, never mind.

"Monads are just monoids in the category of endofunctors, what's the problem?"
> Oh good. A write up that’s aware of how unapproachable nomads seem.

Normally I don't comment on typos (there are more important things in life!), but this one made me chuckle :)

Sorry, fixed. That’s what I get for using my phone to comment on technical things :)
The best practical description of monads I've heard, with apologies as I do not remember the quote's author:

What if, in C++, the semicolon was an operator you could overload?

I've heard Phil Wadler use this description. Why is it useful? To implement Async/Await as a library instead of a language change. Mainstream languages like C# are looking more and more like a collection of DSLs and less like a general purpose language (!).
In C++ the semicolon separates statements. The appropriate correspondence is the comma operator, which separates values and, of course, you can overload the comma operator (much to our general chagrin) so this doesn't seem very enlightening. But this is still only discussing >>, not >>=.
So I haven’t quite grasped monads or their usefulness.

Your “what-if” is completely meaningless to me, and I’m sure it’s my lack.

Could you expound upon the implications?

To me it is like saying: what if you could redefine the period at the end of a sentence....ok, but why?

To my limited non-Haskellian understanding, you can give a block of expressions to a monad. It then decides which expressions to evaluate, in which order, optionally depending on the value they might return to the monad. So you could have a monad which does "evaluate all these in order unless something goes null" (which is a nuisance to type in C-style languages), or one that expands on the if-then-else concept. None of these carry forward internal/hidden state like an IO monad does, but are there to expand user control for how to flow from 1 expression to another in your custom scopes.

It's somewhat conceptually related to Command or Visitor Pattern programming, but Haskell has syntactic sugar to have them basically work similarly to normal statements. Alternatively, this is a runtime-only version of one specific thing that Lisp macros can do to &body scopes, in constructing custom control flow.

This also is only 1 way to use monads, as they're a technically pretty simple tool (an interface with 2 functions) that can be used in a bunch of scenarios.

[If I'm wrong, and I'm probably wrong, I'd love clarifications for myself, too!]

Monads are easy to understand for imperative programmers.

Imagine, you have code "var a=new A(); a.foo(x); a.bar(y); print(a);". How you reimplement it when "a" is readonly structure, not even a object? You will need to implement functions "foo(a, x) -> a" "bar(a, y) -> a", and so on. If you do so, you will have monad.

What's not so easy to understand for an imperative programmer is this: Why should I jump through those hoops just to have a readonly structure? Why not just, you know, program imperatively?

I think this is a big part of the problem explaining monads. If you don't have the problem, you don't care about the solution. And if you're an imperative programmer, this looks like saddling yourself with a problem you don't need to have, and then jumping through bizarre hoops to deal with the complications caused by a set of restrictions that you didn't need in the first place.

I think instead the right approach is to sell people on the benefits of immutability, and talk much less about monads. When they start to need them, they'll start to see why they want them. Once they see them solve some problems that they actually have, then they'll start to get it.

Benefits of immutability are same as benefits of forbidding of global variables: no side effects. If function foo(a,x) cannot have any side effects, then you can compose it freely.

Monad shines with containers, because you can write tons of useful functions, even your own libraries, for a container, which will operate on generic type T, so you will have lot of functionality available out of box for any new type T, which can be wrapped into container, without any additional effort. It's as powerful as templates in C++, but WITHOUT SIDE EFFECTS, so you cannot shoot yourself in the foot.

Another example: UNIX text tools. You can open a text in a editor (e.g. vim or emacs) and modify it in imperative style, or you can use cat, cut, sed, paste, join, and many other tools, which can operate on text, in stream mode, when original text is not modified. Text stream is monad here. Or you can convert (lift) your data to JSON or XML, and use JSON or XML tools in stream mode (e.g. when every record is on it own line): JSON and XML streams will be monads in such case.

It's up to you to modify text manually, or use existing tools for text stream monad.

> What's not so easy to understand for an imperative programmer is this: Why should I jump through those hoops just to have a readonly structure? Why not just, you know, program imperatively?

Beacuse imperative programs are rife with "statements". Opaque lines of code that may or may not do something, somewhere, which cannot be manipulated as first class objects.

Why would you ever wanna program with that weird restriction?

That's how I describe monads - a programmable semicolon.

Basically {A; B} becomes semicolon(A, B).

Then you can have hidden state in the semicolon function and have complete control of how (and whether) you execute A and B, and in what order.

Well, then you came pretty far already. This post is not a tutorial on monads. It’s really his thoughts on why he sees monads are needed in Haskell. (And not needed in, for example, ML.)
Explanations of monads in the abstract always make my eyes glaze over. But if you look at a few examples of actual applications of monads I think the concept shines through clearly enough.
> Monads in Haskell are used as a mechanism for scheduling evaluation, thus turning a language with a hard-to-predict evaluation strategy into a language with predictable, sequential, interactions. This makes it possible to add interactive computations such as state and input-output to the language, noting that benign (non-interactive) computations are already a part of the language, transparent to the type system.

That's a very operational/imperative perspective, and to my mind is putting the cart before the horse. To me the whole value of monads is that they let you talk about things like state and input-output in a declarative way, using plain old functions and values, rather than having to use these awkward/confusing concepts of "evaluation" and "scheduling".

There is a point to it though - continuations, for instance, have monadic structure - but at the cost of introducing a strict evaluation order. There is other work related to the linear logic that does what Monads do - provide an abstract syntax - but working better with laziness, so it's not exactly wrong to give Monads an operational perspective.

There are many perspectives one can use to understand Monads. The operational one is valid - and useful for many beginners. Of course, it's not the only way to look at them, as you point out.

What is this linear-logic thing that does what monads do? I haven't yet grokked linear-logic (apart from the advert, that you might want to use it to reason about resource constraints), so I would like to read about this :)
I think the commenter is referring to the process of declaring an order for executing a compute graph. With CSP languages this is established by the order in which the statements appear and reference each other, and computation is explicit: all previous statements in a given process have finished executing at any given point.

This is in contrast to Haskell that uses monads to declare the computation graph (well, functions, but monads are under discussion here). In this world, data is computed lazily as needed. The questions you ask about the code center around functions and their dependencies, not necessarily computation order.

A task that is trivial to express with on paradigm might be non-trivial to express on the other: haskell is excellent about expressing lazy computation and side effects, whereas a CSP language will offer easy reasoning about the specific and correct order of execution.

Note I am not a PL expert, just an enthusiast. My diction may be off for the domain.

You might like to look at Clean (terrible name) which is roughly as old as Haskell and in a similar style, but instead of Monads it uses linear types to enforce the order of IO - https://clean.cs.ru.nl/Clean
The operational perspective is valid but it doesn't help to answer the question, IMO. If your answer to "what is the problem?" is "the problem is our hard-to-predict evaluation strategy", then the beginner's natural follow-up question is "why not just use a normal evaluation strategy?", and there's no good answer to that one (indeed many regard Haskell's laziness as the wrong choice; in recent times we've seen post-Haskell languages e.g. Idris moving away from it, and the best-known industrial deployment of Haskell uses a strict variant).

Meanwhile I find monads very useful in Scala, even without any laziness in sight.

If you actually need to care about the evaluation strategy, then Haskell may not be the language for your project.

> Meanwhile I find monads very useful in Scala, even without any laziness in sight.

But do you use monads for all the things Haskell programmers use them for? Or only a subset of those things?

Most projects do not make a language choice based on the needs of the model, but based on the needs of the developers. It's the same reason you've chosen English to write your comment in, rather than some other language that might express it better.
> If you actually need to care about the evaluation strategy, then Haskell may not be the language for your project.

Almost all projects end up needing to care about performance at some point or other, if only a little. (So yes, Haskell is probably not a suitable language for most projects, sadly)

> But do you use monads for all the things Haskell programmers use them for? Or only a subset of those things?

All the things, as far as I'm aware. Certainly the obvious ones like IO.

Most monads I'm aware of have an operational interpretation.

The `Maybe` (or `Option`) monad, for instance, has the single effect

  fail :: Maybe a
  fail = Nothing
as soon as `fail` is "evaluated". Evaluation here is in the monadic sense - and is not related to the evaluation strategy of the host language.

List monads have a non-determinism operational interpretation. The `State`, `Writer` and `Continuation` monads obviously do as well.

You could say the `Reader` monad has the single effect

  ask :: Reader e e
but thinking about this operationally is certainly not that helpful as this returns a single constant value from the environemnt.

The same thing goes for `Free` which is essentially a data structure for the abstract syntax of Monads. However in some sense even for this part of you understanding of free monads is likely based in providing a natural transformation to an interpretation in another monad (i.e. `iterM`)

Like I said, operational interpretations are valid, but they don't help explain why monads are useful, because if you only want the operational interpretation then why not build the operational functionality directly into the host language (particularly since most host languages already offer that kind of thing)? E.g. the operational version of Maybe/Option just seems like an overcomplicated version of exceptions - if all you want is a "fail" effect, why not just have it throw an exception and avoid all the extra monadic plumbing.

(I believe there are good reasons you would want to use monads, but only from a denotational perspective, not an operational one)

It is a completely wrong-headed perspective. Monads are just an abstraction, they do not "turn the language" into anything; code that uses monads is normal Haskell code evaluated with the same lazy strategy as all Haskell code.

Haskell also does not need monads to do IO as the author suggests. If you made monads impossible to express (as first class concepts) in Haskell, say by blasting HKTs out of the language, you would still do IO the same way, just with concrete functions instead one's parameterized over an arbitrary monad. If you made Haskell strict, you would still do IO the same way (as an unrelated note though, I believe you would need to add an unbounded looping combinator uloop : (a -> IO (a, Bool)) -> a -> IO a)

so you are telling me if getChar::Char haskell wouldn't return the same char all the time?

Or are you telling me it needs a rather elaborate type signature(World -> (Char, World)), which is entirely the point the author is making here why haskell as being a lazy language needed monads but not ML.

In a pure language, whether lazy or not, x: T always "returns" the same value x. getChar: IO Char always "returns" the same element of IO Char. The sense in which getChar returns a Char belongs to the semantics of the "inner" language associated to the IO constructor and the properties of the "outer" language, like whether it is lazy or "has monads" or even is itself impure like ML, have little bearing on it.

Again, monads are just an abstraction. A language does not need monads to do IO because it is pure or lazy. And a language does not not need monads because it is already impure.

My point is that lazy semantics memoizes evaluation, so you need an elaborate signature to make sure that it can't(which is how the IO type looks like). The "inner" type of IO is very much relevant to the semantics of the "outer" language.
The evaluation semantics of a language are not something you can change by giving some king of elaborate signature. Anyway, we don't need some elaborate type, a minimal IO can defined freely from the combinators

    getChar: IO Char
    putChar: Char -> IO ()
    pure: forall a . a -> IO a
    ap: forall a b . IO a -> (a -> IO b) -> IO b
 
which makes a value of IO a just a common tree. The semantics of the inner language (written [[x]]) can be given in terms of the semantics of the outer language (written [x]) by

    [[p: IO a]] is a program that produces an [a] (or diverges)
    [[getChar]] := program that reads a character, c, from the input and produces c: [Char]
    [[putChar c]] := program that:
        1. "runs" (see below) [c]: [Char] producing a character
        2. prints that character to the output
        3. produces [()]: [()]
    [[pure (x: a)]] := program that produces [x]: [a]
    [[ap (x: IO a) (f: a -> IO b)]] := program that:
        1. runs [[x]], producing y: [a]
        2. then computes p = [f] y: [IO b]
        3. then runs the program [[p]], whose result is the final result
The semantics of the "inner" language do not have any effect on the semantics of the "outer" language. The semantics of the inner are strictly layered on top of the semantics of the outer. But what I said was that the semantics of the inner language do not depend on the properties of the outer language such as purity, evaluation order, etc., which is certainly so. The only things that the inner semantics really require of the outer is that

* we can apply f: [a->b] to x: [a] to get f x: [b]

* there is a way to regard a character c as a member of [c]

* there is a way to "run" an element of [Char] and get a character, in some sense appropriate to the outer language. For normal pure functional languages [Char] will just be the set of characters plus possibly some additional data to model non-termination so this is easy, but it works even if the language is impure (in this case, the effect of running [c] would precede the effect of printing), if it is non-deterministic (in this case, the output stream bifurcates whenever you print a character), etc.

There is no place in which whether the outer language "has monads" enters in.

(Unlike the above list of requirements, where the notion of a monad has certainly entered in :-) But that is what Moggi used them for after all.)

It seems that you have built the IO type and it's monadic properties into the language? I am talking about the concrete type of IO has and how using it, without the monads as abstraction would be so cumbersome that haskell "needed" monads.

You can't change the semantics but you can emulate the desired semantics, which is what i argue IO type does.

> I am talking about the concrete type of IO has and how using it, without the monads as abstraction would be so cumbersome that haskell "needed" monads.

It would be comparable to working with e.g. Lwt in OCaml, which is very much a practical way of programming.

I don't think this is accurate. Haskell "cheats" by making IO special and magical [1]. GHC has this definition:

    newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
The Haskell standard, last I heard, specifies the implementation of IO as being compiler-specific. I may be wrong, but I believe you can't reimplement IO yourself in pure Haskell, at least not the way GHC needs it to be defined.

As the article points out, this has little to do with monads. You can force ordering using unique types [2] (e.g. used in Clean) by having I/O functions take a file handle and return a new file handle in every call. But IO fits nicely into the monadic pattern.

[1] http://blog.ezyang.com/2011/05/unraveling-the-mystery-of-the...

[2] https://en.wikipedia.org/wiki/Uniqueness_type

I'm not really talking about Haskell. Certainly not GHC.
No, it's more that getChar :: IO Char does not need to know anything about "monads"—it only depends on having some IO type for "IO actions" or "IO procedures" or whatever you want to call them. Combine the concrete IO type with a few built-in library functions for building larger IO actions out of smaller ones and you have a perfectly good (albeit unextensible) effect system without every saying the word "monad".

Interestingly, the programming experience with this is going to be pretty much the same as using a language with promises. It would be like working in JavaScript with no blocking IO, but with some abstraction and syntax sugar to make it less powerful.

Or, more pointedly, it would be like working in OCaml. OCaml might not "need" monads, but all serious OCaml is full of monads anyway, at least depending on LWT or Asyc for concurrency.

> Combine the concrete IO type

Why does it need that concrete type (taking one world and producing another)? Why can't i just have it's concrete type be Char? (obviously you would implement with some unsafe inside). But does referential transparency still hold?

Well, as you said in your earlier post, referential transparency can’t hold unless you only ever want to `get` one `Char`; that’s the definition.
Haskell doesn't "need" an IO type—it wants one. The IO type gives us a language-level way to separate code that has side-effects from code that doesn't. It's a powerful tool to help manage complexity in our programs.

One of the advantages of keeping IO in an explicit IO type is that code that isn't in IO becomes easier to refactor. This is what people mean by referential transparency: since I know that a `Char` cannot implicitly depend on the state of the rest of the program, I am always free to move it around, put it in a variable or replace it with an equivalent expression without changing the meaning of the program. You would not get this with getChar :: Char because two getChars could give you different results, so moving them around or reordering them would change the semantics of your program.

> it only depends on having some IO type for "IO actions" or "IO procedures" or whatever you want to call them.

It needs some IO type that you can put stuff in it; when that stuff is functions you need some way from passing previous results as arguments of the next ones; also, you need some way to compose that stuff in a defined order.

You do those, and you have a monad. Other languages do not work without monads, they just get a single IO monad that everything is implicitly inside. Their programmers can afford not to say "monad" because they only get one to choose, and can't choose not to take it either.

But the point is the language doesn't need monads. You could express the same solution to the problem more verbosely without monads if the language couldn't express monads. Consequently, monads are beside the point.

Haskell needs to constrain the order of evaluation when it's dealing with the real world. There's a solution to that problem (require and yield `World`). Then we choose to use `IO` (i.e. a monad) to implement that solution.

But the abstraction that is a monad is independently justified and worthwhile in languages like Typescript (I wish!) and ML which don't have the same IO issue.

As long as we teach monads as if they have anything to do with IO we're confusing people. IO uses monads because it's convenient and apt. But monads don't control order of evaluation. They've got nothing to do with order of evaluation.

> Then we choose to use `IO` (i.e. a monad) to implement that solution.

Yes, we are in agreement, however i am arguing that haskell had to use monads here otherwise it would be very difficult to use.

> As long as we teach monads as if they have anything to do with IO we're confusing people.

Sure, but the introduction of monads in haskell came out of a real necessity is my point. Category theory is powerful tool for abstraction and useful in any language.

> But monads don't control order of evaluation. They've got nothing to do with order of evaluation.

Right, but they can hide the trick and make it all seem transparent, which imho is very important point to keep in mind.

Haskell does not constrain the order of evaluation when dealing with real world. It constrains order of effects.

The code "putStrLn this >> putStrLn that" is allowed to evaluate this and that in any order, basically. "that" may depend on "this" and vice versa. The very expressions "putStrLn a" is also not an action, but expression, being evaluated into actions.

And the constrainment of effects is what makes Haskell special.

World -> (World, Char) is one possible type for getChar. There was an approach called Fudgets [1], based on stream processing, and in this case the signature would be getChar :: SP Response Char.

Stream processing type used in Fudgets also admits monadic interface implementation.

[1] https://research.chalmers.se/en/publication/1015

Yes, it doesn't at all seem right.

After I learnt Haskell, I was forever wishing the languages I work in professionally had monads. But they're all strict languages. Monads give us a nice consistent way of working with stuff inside generic wrapper types (e.g. nullable, arrays, classes and other partially applied functions, state machines, data paired with a world, partial functions converted into (more) total functions by tagging success and failed operations). My professional languages have different solutions for all of them and so it's harder to separate the rules and the mechanisms with heavy syntax

It's more that it's very convenient to think of side effecting functions operating on a world and returning a new one once you already have this powerful abstraction.

I like the fact that the type system encodes that the function has a side effect. But I don't particularly care that the language is lazy when I enjoy this benefit. They're quite independent.

Unfortunately, the Haskell wiki is not a great place to point beginners. The wiki's growth was rather... organic.
> Some of the words above, especially the meandering second sentence, seem to be written to baffle rather than inform.

Ironic. The message he's referring to is the only explanation for what a monad is that has made any sense to me.

> The essence of monad is thus separation of composition timeline from the composed computation's execution timeline

I also feel that it makes sense. Haskell prefers expressions to statements. When needed, statement-like things are constructed from expressions.

Monads are a way to model effects -- to mark a computation as using IO (or not), as being parallel (or not), as having exceptions (or not).

Monads in Haskell are used as a mechanism for scheduling evaluation...

This is only partially true. Any language that tries to be fully declarative -- which is to say, that tries to make all effects "part of the value" (and in consequence, part of the type) -- faces the same problems as Haskell. Idris, a strict language, uses monads to model effects.

The C language doesn't make effects part of the type signature, but C program verifiers do add effects modeling. This can be with regards to aliasing (which writes/reads could affect another pointer?), termination or timing.

It's the underlying interest in program verification that drives the use of monads in Haskell, in part because it's program verification that drives the interest in Haskell to begin with.

I can’t upvote this enough. I always wish monads has been described to me this way from the get-go, it would have saved me a lot of time.
After many years of Haskell, I was playing around in C the other day for fun. I'm writing low-level network code, and my code looks like this.

int err = socket(...) if ( err < 0 ) { do_something; }

err = listen(...) if ( err < 0 ) { do_something; }

Haskell's bind operator >>= makes abstracting this really simple. I wish I had it in C.

:D I think go did a good job of internalizing this
Could you elaborate? As a C guy, I'm interested what you think would be simpler than that simple code you posted. Structurally, what you just posted (minus the details about what "socket" and "listen" mean) could be understood by a child. I'm skeptical the corresponding Haskell >>= construction would be so understandable.
In Haskell, the 'if (err) { ... }' parts could be factored out into a new bind operator for a new monad stack. Then, you don't have to write these checks every single time. The do notation is automatically converted into the corresponding monadic code, and the algebraic monad laws guarantee that the meaning of the code is preserved.

For example, for the `if (err) {...}` cases, this is just the `ExceptT` monad transformer.

With regard to understandability, using error throwing code in Haskell is really easy. Implementing it is slightly more difficult, but so is implementing a C compiler. I mean, we shouldn't judge the utility of a thing based on whether the implementation of the thing could be understood by a child. By that measure, children would be incapable of using facebook, because they could not build it.

So what would the above code actually look like, in your world?

>you don't have to write these checks every single time

But in real-world applications, the way you react to errors would differ from function to function. Some functions would print a notification to a logfile and return early. Some would terminate the program and send an email to an admin. Some would activate a red "offline" indicator somewhere in a program.

In haskell? I'd construct an API that could be used like the following. This looks like the code we write at the place i work at:

     runService=
       s <- note SocketConnectionError $ socket AF_INET ...
       note CouldNotListen $ listen s 5
       note CouldNotBind $ bind s ...
       
       forever $ do
          client <- note CouldNotAccept $ accept s
          response <- serveRequest client
          ...
The signatures of socket, bind, etc, would look like (for example):

    socket :: ExceptT SocketError IO Int
    listen :: Int -> Int -> ExceptT ListenError IO ()
Then, at top level

    main = runExceptT runService >>=
           either handleError pure

    handleError (SocketConnectionError rsn) = <try again, maybe>
    handleError ...
The key here is that error handling is factored out. The 'runService' function expresses concisely all the steps taken, and the entire concern of error handling is factored out into the 'handleError' function, whose behavior I can actually change based on what I may want. In a C implementation, changing error handling strategies, would mean passing some kind of flag in (you could rebuild exceptions using setjmp/longjmp, but meh).
Children aren't born knowing C, and people who learn functional first don't tend to find it any harder. (If anything it's easier, since the meaning of "=" in Haskell is much closer to its usual meaning than the meaning in C).
Yeah, I agree. Functional languages are often more intuitive to beginners. My pet theory is that the reason CS people tend to be somewhat 'odd' is that most people do not think imperatively, so when imperative programming is propped up as the gateway, it screens out people who would otherwise flourish in the field.
> most people do not think imperatively

When trying to solve a problem by thinking it through, don't most people think imperatively? Ie by a sequence of steps (statements) that modify the world around them (state) to reach the goal?

I’m not a psychologist. I can’t talk about most people. I do not. I break problems down into sub problems and then think of how to combine results to achieve the answer. I repeat this until my subproblem is trivial. This lends well to the compositional approach of functional languages. Imperative languages require taking the extra step of scheduling. I think many people think like me but due to the prevalence of imperative languages, do not pursue cs.
Well, scheduling is important. I can't connect my new phone to the computer until I've taken it out of the box it came in.

It feels like I'm leaning to the imperative side. When breaking down the problem into substeps, I'm already thinking about the sequencing which will later dictate the scheduling.

I've been teaching Scratch[1] to 11-13 year olds for a few years now, and most seem to pick it up pretty quick. However a few struggle (while still being motivated enough to keep trying). Would be interesting to see how it went with a functional Scratch version.

[1]: https://scratch.mit.edu/

> Well, scheduling is important. I can't connect my new phone to the computer until I've taken it out of the box it came in.

That's not scheduling, that's a data dependency which functional style handles very well. Imperative would be more like deciding you have to unpack the charger and then unpack the phone and then charge the phone, when in fact it's fine to unpack them in either order. http://www.lihaoyi.com/post/WhatsFunctionalProgrammingAllAbo... has an extended example.

I think codeworld is a more functional alternative to scratch, although much less drag and drop. Check it out.. It's pretty fun! https://code.world/
> people who learn functional first don't tend to find it any harder

Do you have a source? I have a vague recollection of a paper doing some research on that, but cannot find it. If I'm not mistaken, results weren't that clear cut.

C error checks are easy but not simple, you need enormous amounts of QA for every single change to know if you got it right (and then you still dont know)
I hate to be "that guy" but take a look at Rust if you haven't. It's pretty close to C but incorporates a lot of functional-style type magic. Your particular example is very explicitly supported by the `Result` type (which is an error-specific version of Haskell's `Either` type). It also has some syntax sugar and macro usage to make working with error types pretty simple.
Rust is great, but not appropriate for my C use case. I did consider it, and would use it for other systems level stuff. This particular project is very close to the metal, and I want to use C and assembler. But yeah, if it was any more complicated, I would use rust. Rust is pretty great.
This is a somewhat misleading way to think about Haskell and monads—it puts the cart in front of the horse and obscures both how Haskell deals with effects and how monads are more general and useful than just IO. This blog post ends up mystifying more rather than demystifying.

Then again, this shouldn't be a surprise from an article that describes the Haskell ideas as "the Haskell monadic dogma"! You wouldn't expect a particularly charitable or educational account to start like that.

A much better way to think about it is to think about IO without considering monads at all. One of Haskell's core characteristics is that it explicitly separates code with side effects from code without side effects using the IO type. This is a powerful design in and of itself—it's a tool for helping programmers manage effects and not a hack around laziness.

Personally, more tools to manage effects is exactly what I want. After all, even in languages, code I write is carefully organized to separate most side-effects (API calls, database accesses... etc) from the domain logic, but this happens solely through convention and code organization. Having tools in the language to express the same separation is powerful.

Note how I never needed to talk about monads here—IO is the important concept. With this in mind the real question is not "why does Haskell need monads?" but "why are monads a useful abstraction?". This gets a bit long for an HN comment, but I wrote a blog post with my thoughts a few years back. It started like this:

> I believe the notion that Haskell uses “monads” to enforce purity is rather misleading. It has certainly caused quite a bit of confusion! It’s very much like saying we use “rings to do arithmetic”. We don’t! We use numbers, which just happen to form a ring with arithmetic operations. The important idea is the number, not the ring. You can—and most people do—do arithmetic without understanding or even knowing about rings. Moreover, plenty of rings don’t have anything to do with arithmetic.

> Similarly, in Haskell, we use types to deal with effects. In particular, we use IO and State and ST, all of which happen to form monads. But it is still better to say we use “the IO type” to do IO or that we use State for state than to say we use “monads” to do IO or state.

You can read the rest here: http://jelv.is/blog/Haskell-Monads-and-Purity/

This is by no means a definitive discussion of monads or anything like that; it's just a perspective I found useful for understand what Haskell gets by having a monad abstraction and why it's interesting.

If you're looking to understand what Monads are or why they're interesting this isn't the place to figure that out.

Monad: if you have a data structure that wraps some value of any type, then you also have to implement the interface that provides `return` and `bind` (there's talk about requiring another method, `join` but I digress). The data structure is the "context" and the interface is how you compose values from other Monads together using functions. If you can prove your implementation of the interface methods follow certain rules then you have a Monad.

Why is this pattern/abstraction/interface useful in Haskell?

I think there are several reasons. The article touches one of the most talked about: sequencing. Because of the way the rules of the Monad interface are structured you're guaranteed that the functions you use to compose your Monads together will be executed in sequence with respect to their definition in your function body.

(How it all ties together is straight-forward but requires a certain pedagogy and exposition to follow that I won't get into.)

This is, consequently, what makes Haskell the best imperative language in the world. Not only do you get to use it's wonderful type system but you can compose together small imperative programs into bigger, more useful ones.

>Not only do you get to use it's wonderful type system but you can compose together small imperative programs into bigger, more useful ones.

These bigger, more useful programs you can write with Haskell are pretty much unreadable though. There is a reason everyone is not jumping into the functional programming bandwagon, despite mature languages being available for 30+ years.

I wrote a fairly reasonable toy raytracer in it and I found it very readable.

The hard part was that the type system doesn't help you get your algorithms to spit out the correct answer, only the correct kind of answer. After a while I find that errors of that sort dominate the kind of things the type system helps with.

And I'm sure there are type-system-ways-to-solve-that-problem but they're nonobvious and would probably render the rest of the program more confusing. Certainly adding in error handling and state did, compared to working strictly in the IO monad.

Everyone thinks their own code is readable. It's when you jump into a project with code written by 10 other people that you get an idea for a language's innate readability. In my experience I tend to agree that Haskell does not score high on this dimension, its syntax is too terse and too flexible and there are few conventions about how to organize large codebases.
Well, to clarify, I found the code in the libraries etc very readable, obviously I can read my own code :)
A significant number of interesting Haskell libraries I found are thin abstractions over some C api. It makes sense that these would be readable. Is there something for Haskell which serve a similar role as boost to C++? If that is readable, I will be convinced.
I actually disagree. I write very little Haskell but I do read it from time to time, simply because compared to any other language it's simply so readable it's almost fun.

In no other language have I ever got distracted from my main goal by just reading the source code of the libraries I'm using.

While programming, I end up reading the source of most badly documented packages on Hackage that have some interface that looks like what I'm doing. Nearly all that mostly low quality (correlates pretty well with lack of documentation) code is perfectly legible (although some have other problems).

I have never seen any language get near Haskell on the legibility of random code on the internet.

I have found that Java enforces a higher floor of readability than most languages, simply because you have to type out so much "boilerplate" for everything. Though even that does not come close to Haskell. Ironically, the introduction of lambdas made bad Java code using them far less readable.
Yes. On my eyes Java was always the clear choice for enterprise code, because people come and go, and one bad apple does not destroy the entire code base on Java.

After I learned Haskell I had a "I knew nothing!" moment. Anybody writing enterprise code on anything that is not as safe as Haskell (that is, basically everybody) is letting a lot of money on the table.

> These bigger, more useful programs you can write with Haskell are pretty much unreadable though.

This seems somewhat baseless. Unreadable according to whom? There are many large and complex programs written in Haskell by large teams of developers (open source or otherwise). It’s the nature of software in any language to become difficult to manage as the code base becomes large; I don’t think Haskell is an exceptional offender in this category.

> There is a reason everyone is not jumping into the functional programming bandwagon, despite mature languages being available for 30+ years.

There certainly are reasons for that, but not the ones you laid out.

>There are many large and complex programs written in Haskell by large teams of developers (open source or otherwise).

How many of those projects regularly attract new contributors, vs ..say.. something written in python or C++?

> pretty much unreadable

The last readable programming language was Algol 60.

Monad: if you have a data structure that wraps some value of any type, then you also have to implement the interface that provides `return` and `bind`

So it's a wrapper that implements a particular interface? Then why, oh why, do we have so many people who spout, "A monad is just a monoid in the category of endofunctors, what's the problem?" Gilad Bracha covered this in a talk once.

Because of the way the rules of the Monad interface are structured you're guaranteed that the functions you use to compose your Monads together will be executed in sequence with respect to their definition in your function body.

So it's a way of imperatively declaring execution sequence dependencies that lets you reason as if there's only pure functions most of the time. This seems to be the case for all functional programming languages/environments. When you get down to it, they're all, in the words of Garrison Keillor, "Pure, mostly."

> Then why, oh why, do we have so many people who spout, "A monad is just a monoid in the category of endofunctors, what's the problem?"

It's a joke/dank maymay that Haskells still find funny long after it's pissed everyone else off.

The idea is that since category theory is described as "generalized abstract nonsense" by its practitioners, describing monads in terms of their strict category-theoretical definition helps no one, especially those new to Haskell. The problem is that unless your audience is steeped in category theory, they can't appreciate the irony of the situation and you just come off sounding like a dick.

The problem is that unless your audience is steeped in category theory, they can't appreciate the irony of the situation

Who needs mindshare, when you can have in-jokes instead? Better to be the one everyone initially laughs at, but wind up with all the mindshare, than to be the one doing all the laughing who gets left behind.

It's not quite an in-joke. It's in-gallows humour: something common to all transmission of hard-to-communicate knowledge.

Anyone on the far side of a gulf of enlightenment—and understanding monads is an enlightenment, if a small one—knows that it's effectively impossible to actually communicate the particular thing they learned that helped them achieve enlightenment. (Well, it's not impossible to communicate what they learned; it's just that that's roughly useless to anyone else. An enlightenment is a realization that culminates only from all of a set of micro-skills being attained; and each person is missing different micro-skills to start with. You can look back and see the micro-skills you learned, and teach those, but you have no idea what micro-skills you started off already in possession of that others did not—what micro-skills you take for granted—and so you cannot teach those.)

When faced with someone starting on the path to an enlightenment, who asks you to simply summarize the path for them, there's no way to actually usefully tell them. But it's kind of rude to just say nothing—and on a forum like this, equally rude to assume a role of a master verbally lashing an apprentice (ala a Zen parable) for assuming they could understand the concept without working their brain up through all the micro-skills it was missing first.

So, what you do instead is, you make a joke. A joke that seems opaque at the time, but which the learner, having journeyed further down the path, will realize was the truth they sought, and exactly what they asked for—it was just a truth that was, necessarily, completely useless to them at the time. Any such truth would be.

You don't make the joke to be snarky. You make the joke because you wish that, this time, it would work, and the learner would skip the path and achieve the enlightenment; that you could just reach across the gulf and bring them over. But you know it won't. The punchline of the "joke" is not played on the learner, but on the teacher: it is that the world is cruel and to teach skills that require enlightenments is to suffer knowing your students lay across this gulf, and most of what you say will miss them entirely because of the mismatch in micro-skill acquisition between you.

(Though, I suppose, when the learner attempts to teach the concept themselves, and realizes the only thing they want to say is the same useless thing that was said to them—this would be the joke "paying off." That pay-off makes it sort of like a traditional joke, in the sense that choosing to "tell" it rather than simply thinking it to oneself is choosing to set the learner up for that later pay-off.)

An enlightenment is a realization that culminates only from all of a set of micro-skills being attained

There are lots of disciplines/areas of human knowledge and culture where most of the micro-skills have inherent rewards for learning them.

When faced with someone starting on the path to an enlightenment, who asks you to simply summarize the path for them, there's no way to actually usefully tell them.

I think that has to do more with not being motivated enough to try hard enough combined with the difficulty of summarizing. It's one thing to try to explain a paradigm-shifting insight or system to someone who has never paradigm shifted to learn something. The thing about Haskell outreach, are such failures even in the face of an audience who has previously undergone such a paradigm shift.

You don't make the joke to be snarky. You make the joke because you wish that, this time, it would work, and the learner would skip the path and achieve the enlightenment

How is this different from laziness? Smalltalkers similarly gave up trying to convey how their environment was different, many of them with such snarky jokes. Then years later, Chris Granger comes along with Light Table, and transmits what it is quite clearly and succinctly.

1. I perhaps forgot to mention a key thing about the enlightenment process. Most of the people being asked the question are those further down the path, but not further-down by much. Journeymen, not masters.

A journeyman is someone who has acquired all the micro-skills they were missing, and has thus gone on to achieve some level of intuitive grasp of the enlightenment they sought. They "grok" the skill.

A master is someone who has gone back and thoroughly weeded their mental garden of the micro-skills they started with and took for granted, and have begun (though perhaps not finished) replacing those with conscious learnings. The master desires to attain conscious handles onto each and every part of the mental schema or process they seek to understand, in order to understand the enlightenment-requiring mental skill as a system, rather than as a simple intuition.

A journeyman has a sense that they know the answer—that they "are enlightened"—and this sense works well enough to guide them when they seek to use the mental skill they sought to acquire. But, because they do not understand the enlightenment-requiring mental skill as a system, only as an intuition, they can give an apprentice on the path no answer that will satisfy them. This is the "gallows humour" stage of the path.

Yes, masters of the path will be able to guide apprentices. Zen koans are written by masters, and they are no gallows humour; they tilt the world enough to allow you to catch sight of the micro-skills the master was attempting to convey. (In the modern world of analytic philosophy, koans might even have a hope of being displaced by jargon-laden explanations that can be drudged through to bash the concept into one's head, like a maths textbook. That doesn't sound exciting, but it is!)

Light Table is such a koan, created by a master of the path of living-memory-image reflective systems, seeking to "tilt" as many parts of the system that Smalltalk is into the light as possible. (Granger's trick was contrasting those parts to a backdrop of regular, ugly concepts like Javascript and Electron that aren't part of the enlightenment-inspiring system. The concept-handles come clear at the visible seams of the Light Table system. Whereas, in Smalltalk itself, the seams aren't visible, because the system is coherent. You can't see the muscles of the perfect average face; it's too coherent to dissect.)

Putting this another way: most people who might be asked about something aren't teachers with education degrees. They're just students who already learned something and want to share what they learned. They fail because the thing is hard to share, and they don't have the skills about teaching required to realize what's making the thing hard to share and to fix it. (Those that do have such skills, but don't have the time to apply them to the concept—dissecting it and re-building it in a teachable-to-others form—usually just keep silent, rather than attempting to share their learning.)

> Haskell['s journeymen] are such failures [to teach the required micro-skills] even in the face of an audience who has previously undergone such a paradigm shift.

Ah, but have they? Many people have a natural mind for software engineering. They take for granted the concepts inherent in procedural code execution on a CPU or virtual machine; in parsing and lexing; even in pseudo-paradigms like OOP.

Indeed, it is the rare software engineer who actually experienced any paradigm shifts regarding Computer Science (for those of us that took a degree in it.) Usually it's "easy"—meaning natural—for those of an analytical mindset.

All, perhaps, except for that one class you have to take at the beginning of a CS curriculum, Discrete Maths. A lot of the SwEng "naturals" struggle at that. Because Discrete Maths is not the same thing as CS. Discrete Maths is, in fact, maths.

Mathe...

Light Table is such a koan, created by a master of the path of living-memory-image reflective systems

Two answers. 1) So then someone should create a Haskell Koan which has just as much popular appeal and broad intuitive popular understanding. But then again, not really, because: 2) Really, get off of this Koan nonsense. It was really just a very well thought out and effective demo which chose exactly the right way to get across the benefits of such a system.

Whereas, in Smalltalk itself, the seams aren't visible, because the system is coherent.

Oh, there are seams! And warts!

Indeed, it is the rare software engineer who actually experienced any paradigm shifts regarding Computer Science

Really? It sounds like you haven't experienced too many of those kinds of paradigm shifts. All you know is the math-y kind.

Haskell's design and ecosystem "reflects" enlightenment on category theory, somewhat like Smalltalk "reflects" enlightenment on living-memory-image OOP. Neither system teaches those skills, though.

Absolutely wrong. If you delve into the Smalltalk image and class library, it does teach you OOP!

The long and short of the way to communicate all the Haskell "stuff" efficiently, not just monads, is to:

1. give the learner the meta-skill...

2. throw a Category Theory textbook at the learner...

Either put up or shut up. Anyone can have an esoteric uber language off in a corner somewhere and tell themselves the world misunderstands them while circle jerking with like minded folks. Been there, done that. The whole world is chok full of little groups like that. Stopping at just that is not that which makes a difference in the world.

What you are saying seems to amount to: "We're awesome! We're the superior learners, which is why we're superior, but alas poor us, it also makes us suck at teaching." I call BS. It doesn't matter if your tools are superior, if their qualities and the community's qualities make them inherently inferior at gaining mindshare. Either they're so superior, everyone will take the time to adopt them, or they're not so superior that it really matters that much.

Try and produce stuff. Try and reach people. The world will see who succeeds. Simple as that.

> It sounds like you haven't experienced too many of those kinds of paradigm shifts. All you know is the math-y kind.

You know that HN readers are about the 99th percentile of "willingness to learn random new CS concepts", right? Most programmers know one language. In fact, most programmers have only ever worked for one company, on one product, for their whole productive careers so far. Your idea of an "average" programmer is, in fact, a rare programmer.

> If you delve into the Smalltalk image and class library, it does teach you OOP!

That is the exact equivalent of reading a textbook on the subject, except it's not presented in prerequisite order, so it's slightly harder to digest.

A system that teaches is a system that allows you to notice the micro-skills you're missing faster than a textbook. A textbook is the brute-force approach.

Smalltalk is not a system that teaches. It's just a system. You can get out what you put into delving through it, but you can do that just as well with any random system. To be pedagogical, a system has to accelerate that process.

> We're awesome! We're the superior learners, which is why we're superior, but alas poor us, it also makes us suck at teaching.

Er, no: people that "know Haskell" (category theory) generally suck at teaching Haskell (category theory) because people suck at teaching by default, because they haven't learned the meta-skill of teaching. And even those that do, haven't yet put in the effort to factor their mental-model of a given skill to turn it into a teachable skill.

The people that can teach you something about Haskell (category theory), are, y'know, teachers, that have learned Haskell (category theory) and then applied educational principles to their understanding of it in order to be able to teach it well.

Has nothing to do with Haskell, other than Haskell actually having a relatively-difficult skill in it for people coming from a SwEng background to learn. Other languages are easier or harder for such people to learn, because the skills they require are more or less natural given a SwEng background; and, comparatively, people with a CS or Physics or Electrical Engineering background have other backgrounds that make different languages have a different skill-gulf. Haskell (category theory) is easy for mathematicians. Assembly is easy for electrical engineers. Prolog is easy for DB systems programmers. Etc. Nothing special about any of them—they're just different points in knowledge-space, that different people start out closer or further from because of their backgrounds.

> Try and produce stuff.

I do! Not in Haskell, though. Despite writing the above, I have literally never used Haskell once in my life. I'm just talking about it as a specific application of the general principle of skill-gulfs.

> Try and reach people.

Why?

In all of the above, nobody ever said why anyone is trying to teach anyone else monads. Honestly, I think people just shouldn't bother. No "amateur teacher" is attempting to teach anyone else graph theory, or linear algebra, or the x86 ISA. Professional teachers, at universities, do that, because those are skills independent of any programming language. People generally understand that teaching these skills is the job of professional teachers, and that you have to apply yourself as a student, full-time, to learn them.

Well, category theory is such a skill.

In short: stop trying to teach people monads. Petition more schools to teach people monads (category theory), outside of the context of any particular language. Then Haskell is just a language, with no skill gulf.

To say that you should "reach people" with an explanation of monads, is like expecting RDBMS docs to "reach people" with an explanation of relational algebra. It's not their job. You're supposed to come in with that skill. Their job is to pro...

I think this is one of the better explanations of why "enlightenment" in any field is so hard to get to, so hard to explain, yet so compulsively appealing to talk about. The framing around the joke really makes a lot of sense to me.

I'm tempted to share your writing on my other social channels! Excellently done!

> "Who needs mindshare, when you can have in-jokes instead?"

It's a joke, but more importantly, it's a joke by and for people who are already familiar enough with Haskell. It's not a serious attempt at communicating something useful about the language; these attempts exist, but this joke is not one of them. Absolutely no-one makes this joke to beginners. You only make this joke to someone who already has an understanding of Haskell. It's not a common joke, either.

It's like those obfuscated C examples. Nobody uses them to teach beginners anything, and it'd be a mistake to ask "why do all these C programmers keep writing purposefully obfuscated code to scare beginners!?". Purposefully obfuscated C code, much like the Haskell joke, is not aimed at beginners.

As a "never touched any functional language but always hear about how monads are extremely complex", I feel enlighted.
> category theory is described as "generalized abstract nonsense" by its practitioners

That should be more accessible for people trying to learn it. The idea that "category" by itself is something that is supposed to be completely devoid of meaning is not clear at first. This slowed me down a lot when learning.

> describing monads in terms of their strict category-theoretical definition helps no one, especially those new to Haskell.

I am a counterexample: the description of a monad as a monoid in the category of endofunctors actually helped me, especially when I was new to Haskell. I am a mathematician, and happen to know category theory enough to appreciate this description. Also, I am a little tired of hearing "you don't have to know category theory in order to learn Haskell". What if I already DO know category theory? I wish someone wrote a tutorial or a book explaining Haskell to mathematically inclined readers and not dismissing (in a kind of anti-intellectual way) simple algebra as "useless abstractions".

'So it's a wrapper that implements a particular interface? Then why, oh why, do we have so many people who spout, "A monad is just a monoid in the category of endofunctors, what's the problem?"'

"Monad" seems to be just beyond the complexity event horizon where it is difficult for people to get a really clean idea of what it is, so instead we've got dozens of ideas of what they are floating around, probably a good two-thirds of them with fundamental errors in them, which then contributes to the general haze surrounding the ideas.

I've got a mostly-written blog post on deck with the tentative title "Functors and Monads For People Who Have Read Too Many Tutorials", for which around 80% of it isn't explaining what they are, but systematically walking through all the errors I've seen (some of which are in the type notation for Haskell itself; the "[a]" abbreviation for what should be "[] a", if not "List a", alone has done untold damage to comprehension) before finally getting down to what the things are.

(Functor in particular is so trivial it's hard to believe it's that trivial when you finally really get it. Monad legitimately has a bit of a twist in it, and some of the specific uses of that interface take that twist and hammer it home to produce some very funky code, so the fact that it's a bit foggy isn't that big a surprise. The fog that functor has picked up by association with monad is much, much larger than the concept itself, though.)

"So it's a way of imperatively declaring execution sequence dependencies that lets you reason as if there's only pure functions most of the time."

It turns out no, that's just one use of them. A very big one, a very natural one, and the one that drove their prominence in the current debate... but it's not actually what the interface is "about" per se. For a good counterexample, consider the parsing monad implementations that "declaratively" [1] create a parser. It's not really about declaring execution sequences.

(There's also a non-trivial amount of confusion that arises from the fact that monad and laziness have a lot of interaction. The monad interface in a strict language would be used differently than it can be in Haskell.)

Functor, Monoid, and Semigroup are the worst. There's a complete lack of literacy when it comes to abstract algebra in practical programming. Monoid is so incredibly simple but the jargon and terminology is what makes them scary to newcomers.

Monoid: A data structure wrapping a value of some type that implements the Monoid interface. The interface has two methods: `identity` and `append`. If you implement those methods for your structure and your implementation follows particular rules then... you have a Monoid.

Why are they cool? You can append a lot of things together; not just lists. You now have a generic, binary append operator `mappend` that can join together anything that implements the Monoid interface.

What can you use it for? Well.. lists for a start. Configuration? Sure! Load up configs from the shell environment, the `~/.myapp` file, and the `/etc/myapp` file; concatenate them together with your generic `mappend`... boom. Streams? Sure. Trees? Why not. If you can implement the interface you get the operators. You code can stop caring whether it's a this or that. It can focus more on do this, do that.

Semigroup: a monoid without the `identity` method in its interface.

Functors, also a shockingly simple concept hidden behind jargon.

Let me try Functor: You have a structure of values of some type. You want to apply a function to these values inside the structure, but you don't know how to do that. Luckily the structure itself does, so you just give your function to the structure and it applies it to its values for you. Structures that know how to apply functions to its values are called Functors.
I don't think it is jargon that makes understanding difficult. It is efforts required to develop a sense or intuition for those concepts. Like when you are looking at python list comprehensions and see list monad, looking at Go's `if err != nil then return err` and see Either monad and so on.

I've developed such intuition twice and it evaporated both times, when I wasn't using it for long enough.

A monad is a standard interface for the Interpreter pattern.

That's why you would want it. That's what you gain from them. It is a very easy standard to implement if your interpreter is simple, it can be composed into more complex interpreters, and languages that support it come with many basic implementations that you can use to construct something more complex.

The mathematicians are saying basically the same thing, but from another point of view (monoid on the category of endofunctors means computations, with an environment, that you can join one after the other) that focus much more on standards than on usability, and much more on formality than on how to make large structures out of it, so the names are different.

> "Gilad Bracha covered this in a talk once."

Note that Bracha, knowledgeable as he is in his areas of interest, is painfully dismissive of things he does not understand. I watched that talk where he mentions monads (as well as non-optional static type systems, another subject he dislikes), and he comes across as ignorant and dismissive. I wouldn't use his opinions on these subjects as evidence of anything.

Realising that a monad is just a lax functor from a terminal bicategory also helps.
> So it's a wrapper that implements a particular interface?

There's no wrapper, and "interface" is misleading since it suggests something that lets you invoke methods on instances whereas actually one of the core monad functions (pure aka point aka return) is something you call with a value that currently has no connection to the monad.

> Then why, oh why, do we have so many people who spout, "A monad is just a monoid in the category of endofunctors, what's the problem?"

The categorical thing is just a joke. It's hard to talk about monads in general because they're very generic, and unfortunately IO is one of the most "monadey" of them all: it's a monad and essentially nothing else. The best way to learn to work with IO is probably to learn to work with other monads first - option or either or writer or state, things that have a canonical presentation in terms of normal functions and values. Unfortunately, even though I/O is a tiny fraction of real-world business programming (when was the last time you actually used "getChar" at your day job?), it's where people tend to start programming.

> So it's a way of imperatively declaring execution sequence dependencies that lets you reason as if there's only pure functions most of the time. This seems to be the case for all functional programming languages/environments. When you get down to it, they're all, in the words of Garrison Keillor, "Pure, mostly."

You've got it backwards. It's a way of expressing operations that lets you reason as if you were calling imperative procedures most of the time, but they are and will always remain be pure functions in a guaranteed sense that you can fall back on. There is nothing "mostly" about it and this is really important, it's what makes functional programming so useful.

> There's no wrapper, and "interface" is misleading

True. I used a convenient lie to make a point. If you're interested in learning Monads you should definitely learn it from a proper source like Haskell from First Principles [0], an online course, or someone who really knows what they're talking about.

As I only alluded to and others have pointed out: there's nothing about monads that's inherently sequential or having to do with ordering operations. That's just how the language defined the interpretation of the IO monad. And it also happens that this is the primary topic of concern of TFA.

You might even come to realize that much of what a Monad isn't contradicts the entire premise of TFA... Haskell doesn't even need the IO monad and could implement IO other ways just fine [1].

[0] https://haskellbook.com [1] https://donsbot.wordpress.com/2009/01/31/reviving-the-gofer-...

>So it's a way of imperatively declaring execution sequence dependencies that lets you reason as if there's only pure functions most of the time.

No. Monad is just a “design pattern”. It’s nice that this design pattern happens to be useful to force sequential evaluation, but it’s just one of the many things that fits the monad abstraction.

> So it's a wrapper that implements a particular interface? Then why, oh why, do we have so many people who spout...

Its because its actually a mathematical concept. And that's the mathematical definition.

So Haskell decided to use the same name. Now some people feel, should I explain the math concept, or its concrete representation in Haskell.

Also, its a bit more involved as an interface. In that its not enough to implement the common methods for the interface. You also have to prove that your implementation for each exhibit certain properties, specifically there needs to be an identity value which act as a neutral element, and they must be associative.

Those laws are where things get more mathematical. Because its rare in comp-sci to talk about laws and properties of functions.

>Because of the way the rules of the Monad interface are structured you're guaranteed that the functions you use to compose your Monads together will be executed in sequence with respect to their definition in your function body.

No! Monads don't guarantee sequencing. Some particular monad instances do (e.g. IO). But there's nothing inherently sequential about monads. See e.g. point 10 of the following:

https://wiki.haskell.org/What_a_Monad_is_not

I knew I was going to get something about it wrong. Thanks for the clarification. It’s just one aspect that gets focused on because... IO and that's what the article focuses on. I got wrapped up in it too.
The page on "What a Monad is not" has numerous inaccuracies. I wouldn't take it too seriously.
What inaccuracies are there in section 10?
A side point since the author pointed to Agda as being more pure than Haskell:

In many practical situations, computations have deadlines, which may either be a timeout or the user giving up and cancelling. Languages that guarantee that functions terminate aren't as useful as you might guess, because they don't say anything about how long it will take. (It could be millions of years.)

A guarantee that a function will terminate is useful when you can avoid evaluating the function at all, while relying on a function call's existence (without compile errors) as a proof that the result exists.

It's not too often that you want to know that an answer exists without knowing the answer. This mostly comes up in mathematics when proving things.

> Languages that guarantee that functions terminate aren't as useful as you might guess, because they don't say anything about how long it will take. (It could be millions of years.)

That's a theoretical problem, but is it one that comes up in practice? In my experience those languages eliminate the overwhelming majority of long-running functions, so are about as useful as you might think.

I don't think it comes up much in practice because bugs due to non-terminating code don't actually happen much to begin with (compared to all bugs that cause hangs or timeouts), and when they do they are often found via testing. We know that a function terminates for any inputs it was tested with.

But I have little experience with these languages (I've mostly read about them) so I'd be interesting to hear about your experiences.

The "testing will catch it" argument is used as a rationale for avoiding types at all. Of course testing does work, but I find it's more costly and less reliable (e.g. I remember one infinite-loop bug that was missed because it was an overloaded function and the test was invoking another variant).

I wouldn't start porting production systems to Idris just yet, but I do think it's worth adopting the same kinds of techniques: avoiding loops entirely, and making sure that when you do recursion it's very clear from the code what parameter is getting smaller in what sense.

>I don't think it comes up much in practice because bugs due to non-terminating code don't actually happen much to begin with (compared to all bugs that cause hangs or timeouts), and when they do they are often found via testing. We know that a function terminates for any inputs it was tested with.

None of these are acceptable in Agda actually, since they would all be partial functions (i.e. functions that are not defined for some inputs.) The simplest, intuitive explanation is that since Agda has dependent types it needs to be able to evaluate functions during typechecking, and if you allow partial functions (e.g. infinite loops, halting functions, functions that timeout, or functions that are undefined for untested input) the typechecking would be undecidable.

Instead Agda has a bunch of built-in tools to help the compiler figure out if a function is total (the opposite of a partial function, meaning it's defined for all possible inputs). Things like the termination checker can detect a subset of all terminating functions, and sized types let the programmer aid the compiler in figuring this out. There are also language pragmas to overide this totality check on a per function basis.

I've implemented an algorithm, that I knew on paper terminated, and had it rejected by Agda, although I'm not experienced enough to know how easily these things are solved in general.

Yes, static analysis is very useful for understanding what the source code will do for inputs you didn't test. Testing is rarely exhaustive. However, testing will find performance issues where static analysis won't.

To quibble with "none of these are acceptable in Agda": it seems unlikely that Agda or anything like it would detect hangs or timeouts due to code not meeting its deadline. Deadlines and performance constraints are not part of any type system I've ever heard of. Furthermore, performance is dependent on the compile toolchain, the runtime, and what else is running on the same machine, so it's not even well-defined for static analysis unless you make a bunch of deployment-specific assumptions.

A language may guarantee that a function is total in a theoretical sense, but you might not be able to calculate its value for some inputs in practice. (Consider something like "a ↑↑↑ b".) So this guarantee is subtly different from what most users of computer systems would actually want.

Of course, as a another commenter pointed out, this sort of practical bug-finding is not actually what the termination guarantee is for.

> Yes, static analysis is very useful for understanding what the source code will do for inputs you didn't test. Testing is rarely exhaustive. However, testing will find performance issues where static analysis won't.

Static analysis is a funny word to use about what Agda does.. Remember, Agda has dependent types, so what you're calling "static anaysis" involves evaluating/executing functions. In fact it needs to be able to evaluate/execute any functions beyond value-level in any imported modules just to be able to parse a sourcefile, due to the presence of mixfix-notation[0] and how it affects typechecking.

> Testing is rarely exhaustive. However, testing will find performance issues where static analysis won't.

How do you test a function that takes a natural number? Not an unsigned integer, but an actual countably infinite natual number? For every natural number n you test your function with, you can construct a new natural number n+1 which your function is untested for, (literally) ad infinitum.

The answer, of course, is mathematical induction[1] and in the case of more general data structures we have the more general structural Induction[2]. But at this point you don't call it testing, you call it proving, and it's this that Agda is aspiring to.

> To quibble with "none of these are acceptable in Agda": it seems unlikely that Agda or anything like it would detect hangs or timeouts due to code not meeting its deadline.

Yes, your right that actually detecting hangs or timeouts would be difficult or impossible. But that's not what Agda does. Agda only allows functions that the compiler can figure out are total, which can never be all total functions (otherwise we would have a solution to the halting problem I think.)

Basically, functions are either (provably total by the compiler) and hence acceptable. In the case of long/diverging computations Agda just has a timeout threshold, where it rejects a function if it takes too long to evaluate it during typechecking. I'm pretty sure that's what would happen if you tried "a ↑↑↑ b".

> Of course, as a another commenter pointed out, this sort of practical bug-finding is not actually what the termination guarantee is for.

Correct, it's not for bug-finding at all, but rather to keep the type theory/logic sound when using Agda as a proof assistant.

Just to be clear: your criticism makes a lot of sense from a software engineering perspective, but considering the general performance characteristics of Agda, and the almost noneexisiting tooling around it, I think we're a couple of years away from "deployment specific assumtions" being something Agda needs to be too concerned about :)

[0] You can define a function "if_then_else_" where the underscores represent arguments, so you would use it like this: "if TRUTH_VALUE then TRUE_CONSEQUENCE else FALSE_CONSEQENCE"

[1] https://en.wikipedia.org/wiki/Mathematical_induction

[2] https://en.wikipedia.org/wiki/Structural_induction

> However, testing will find performance issues where static analysis won't.

Not my experience, at least if we're talking about constructive type systems (I don't find best-effort-style "static analysis" useful, personally). I don't think I've ever seen any "normal" software testing regime (junit etc.) catch a performance issue that wasn't a straightforward infinite loop of the kind that would be obvious in something like Idris.

> quibble with "none of these are acceptable in Agda": it seems unlikely that Agda or anything like it would detect hangs or timeouts due to code not meeting its deadline. Deadlines and performance constraints are not part of any type system I've ever heard of.

Languages with a more detailed type system that can express maximum evaluation time (in terms of e.g. some set of "primitive ops" for that language) do exist, though I wouldn't consider any of them remotely practical.

> A language may guarantee that a function is total in a theoretical sense, but you might not be able to calculate its value for some inputs in practice. (Consider something like "a ↑↑↑ b".) So this guarantee is subtly different from what most users of computer systems would actually want.

Sure. Requiring totality doesn't eliminate all possible code-runs-for-too-long bugs, only the vast majority of them. I don't see that as a reason not to do it.

To clarify:

By "static analysis" I mean anything you can learn from studying source code without running it. (Though, with Agda this isn't a clear distinction; maybe we could talk about what we can extrapolate about whatever the compiler didn't evaluate.)

I was speaking of testing broadly to include manual QA and running benchmarks. A continuous benchmark will give a graph of the system's performance for certain actions. It's typically not pass/fail, but if the developers notice a regression they can investigate. These slowdowns just aren't correctness bugs as commonly understood and many places don't notice performance issues until after the code goes live and it's noticed via monitoring. (Benchmarks are tricky to write, tricky to run consistently, don't generalize, and miss a lot of things, but nonetheless measure things that static analysis doesn't even try to do.)

"Vast majority": I guess this depends on what you put in the denominator? I can certainly believe that Agda programmers don't see performance issues. But I'll confess ignorance: is this used for anything other than doing math?

I'm thinking more along the lines of: if a more mainstream language had totality checking, what bugs would this actually find in the code that runs in data centers? Plenty, I'm sure. But I can think of lots of things it wouldn't. It's one method of bug-finding among many.

> I was speaking of testing broadly to include manual QA and running benchmarks. A continuous benchmark will give a graph of the system's performance for certain actions. It's typically not pass/fail, but if the developers notice a regression they can investigate. These slowdowns just aren't correctness bugs as commonly understood and many places don't notice performance issues until after the code goes live and it's noticed via monitoring. (Benchmarks are tricky to write, tricky to run consistently, don't generalize, and miss a lot of things, but nonetheless measure things that static analysis doesn't even try to do.)

Well we've got to compare similar levels of effort. I think most "normal" teams would find it easier to adopt a more detailed type system than to set up a continuous benchmark and use it effectively. I could be wrong.

> "Vast majority": I guess this depends on what you put in the denominator? I can certainly believe that Agda programmers don't see performance issues. But I'll confess ignorance: is this used for anything other than doing math?

> I'm thinking more along the lines of: if a more mainstream language had totality checking, what bugs would this actually find in the code that runs in data centers? Plenty, I'm sure. But I can think of lots of things it wouldn't. It's one method of bug-finding among many.

I don't know so much about Agda specifically. I agree with thinking about mainstream code that runs in data centers. My thought process was: of the code-runs-too-long bugs I've seen in my career, how many of those could have happened if I'd been working in Idris with totality checking on? I can only think of one (which was due to regex catastrophic backtracking), and I can think of several that would have been caught (the case where the test used the wrong overload of the function being the most production-impactey one).

My experience is that while other methods of finding bugs do exist, the cost/benefit for type systems is just so much better than anything else that I wouldn't even think about using any other technique until I'd tried type systems first.

The reason for banning nonterminating functions is different, logical not computational. If you want a programming language to work as a proof checker then you want its type system to be a consistent logic, so that a program P of type A can be used as a witness that proposition A is true. Using non-terminating computation you can create such witnesses for any type, including whatever you may want to represent "False". So you have an inconsistent (useless) logic.
If you think Function Composition is a good thing, then it is very natural to want composition for functions of more interesting types than T→U, for example T→Promise[U] which chains intuitively but not by type.

How did I do?

I don't know, but I think "Chain" might be a better name than "Monoid" to introduce the concept and capture the main idea.
Well “Monoid” is a different sort of composible than something like like T -> Promise U.

I do agree that Monoid and Monad aren’t really great names for anyone who doesn’t come from an algebra background (ie basically anyone).

I like to think of monads as a supporting sidecar wrapper. It's almost like a plugin system; you can extend some base computation to have additional functionality for free.

This becomes really obvious how much boilerplate can be "lift"ed in something like the Flare library for PureScript (https://david-peter.de/articles/flare/).

Ex:

lift2 ( * ) (int "a" 6) (int "b" 7)

This generates 2 text fields, hooks up the change events, plumbs them into a third UI component that renders the product of the 2 fields.

Fundamentally we are just saying we want "a * b" but the monad / sidecar does all the extra work to make UI components and hook up the change events.

It's a way to wrap values in a mathematically defined way.

You can do it differently if you like, but you have to define the semantics yourself and find out about edge cases that math has already solved, which is the problem that is searched here, I guess...

It was once said of philosophers, that they first raise a dust and then complain that they cannot see. I was reminded of this while reading some of the discussions here.
does haskell support bofa monads?
I'd say the problem with all these Monad explanations is the people reading them are in general object oriented programmers (like me) and the problem that exists in functional programming just isn't an issue in C++ / Swift etc. Its never explicitly stated because functional programmers are just so used to it, its like air.

So in a very naive way - here's the problem, When you apply a function and get a result you have no state - the function doesn't remember what happened. In object oriented programming you always have the state - its in the object, or other objects floating around, and methods can always reach out into surrounding objects to get bits of state it needs, pure functional programs can't do this - they have no state except the things that are passed as parameters. The haskellian way around this is to pass around the context of what just happened - vaguely the context is the surrounding bits of data you need to keep track of.

Monads, functors and so on are the ways that haskell keeps and manipulates the context and the value. The rules also have a very nice mathematical theory which they fit into. Most haskell intros start from maths and shoe horn the code into this - its not really necessary. This clarified it for me http://blog.sigfpe.com/2006/08/you-could-have-invented-monad...

I think one thing one should ask is:

If monads are the solution to X, and we add a different solution to X to the language, then what are monads for?

Let’s say you have imperative IO. Then promises are a Monad but then you could just add language support for async and promises are not really needed as a Monad anymore.

A more functional replacement for monads could be something like algebraic effects. These could replace monads completely or they could be like multi core ocaml and only give you one shot continuations (so not allowing for list or other multi-shot monads).

In this context I like to think about the continuation Monad. In Haskell:

    newtype Cont r a = Cont { runCont :: (a -> r) -> r }
This means “some computation yielding a r which got paused part-done having just yielded an a. In some sense all monads are expressible in terms of Cont. I feel like this is largely the essence of why it is useful to have monads: they express computations which happen with some context, able to switch back and forth between things in the context and things in the normal computation. I don’t think sequencing evaluation is so important to the definition.
The question monads answer is: How do I make the components of my program recombinable in ways that the result is also a monad.

Monads are like LEGOs, you can combine them in any way and what you get is something to which you can attach still more lego-pieces. It doesn't matter how exactly lego pieces are able to connect together. What matters is that you can combine any Legos together with that same mechanism and get as result something to which you can attach even more Legos. Maybe a better name for Monad would have been "Lego".