Readit News logoReadit News
brooke2k · 2 months ago
As far as monad tutorials go, this one seems quite good. I like the categorization of monads between "containers" and "recipes".

However, I personally think that monad tutorials tend to give people the wrong impression and leave them more confused than they were before, because they focus on the wrong thing.

A monad is not a complex concept, at all. IMO a more useful way to present the topic would be with one separate lesson for every common monad instance. Start with Maybe, then IO, then maybe State and List, and so on... because ultimately, every instance of a Monad works very differently. That's why the pattern is so useful in the first place, because it applies to so many places. (Note: this is a criticism of monad tutorials in general, not this one in particular, which seems to do a decent job on this front).

In my experience, people new to Haskell focus way too much on getting the "a-ha" moment for monads in general, when really you want a bunch of separate "a-ha" moments as you realize how each instance of a monad takes advantage of the pattern differently.

I also tend to think that monads are best demonstrated in Haskell rather than in other languages, if only because the notation is so much less clunky. That may just be me though. (EDIT: well, also because almost no other languages have typeclasses, so you have to approximate it with interfaces/traits/etc)

Also FYI: in part 2, the code examples have extra newlines in between every line, which makes it hard to read (I'm on firefox, if that matters).

aeonik · 2 months ago
I think you are right. I don't think I've fully mastered the concept yet, but what you are saying resonates with me.

I've been trying to grok monads for almost a decade. More and more I'm beginning to realize how "mundane" the concept is, and the usefulness really is just that specific pattern of mundanity.

Similar to pipelines on Linux, they are pretty basic, but their ubiquity and their use in composing unrelated things together is really, really useful, and you only get that if you use them in a variety of different ways.

Monads are extra cool because of the mathematical rigor behind them, and that's what I'm still trying to appreciate.

cynicalkane · 2 months ago
What helped me grok the mathematical rigor is: If you have a series of monad operations that exist purely in monad world -- in Haskell, if your expression is parametric over the type of the monad -- you shouldn't have to worry about how you do it.

This is what monads being categorically commutative ("a monoid in the category of endofunctors") buys you. You want to turn monad X into monad Y? Sure, just join, flatten, return, bind in whatever way makes the type checker happy. Anything that only uses what's in the Monad typeclass must necessarily be a monad morphism, so if you're generic over your monads, you get that for free. And of course `fmap` and `bind` are required to be parameterized monad morphisms, so there's a lot you can get for free.

macrolocal · 2 months ago
Yep, you need category theory to express something as trivial as the definition of a monad.
pdhborges · 2 months ago
If all monad instances work differently what is the value of the Monad interface? What kind of usefull generic code can one write against the Monad interface.

Related: https://buttondown.com/j2kun/archive/weak-and-strong-algebra...

tel · 2 months ago
The more constrained your theory is, the fewer models you have of it and also the more structure you can exploit.

Monads, I think, offer enough structure in that we can exploit things like monad composition (as fraught as it is), monadic do/for syntax, and abstracting out "traversals" (over data structures most concretely, but also other sorts of traversals) with monadic accumulators.

There's at least one other practical advantage as well, that of "chunking".

A chess master is more capable of quickly memorizing realistic board states than an amateur (and equally good at memorizing randomized board states). When we have a grasp of relevant, powerful structures underlying our world, we can "chunk" along them to reason more quickly. People familiar with monads often can hand-wave a set of unknowns in a problem by recognizing it to be a monad-shaped problem that can be independently solved later.

NickPollard · 2 months ago
Traverse (or foldM) is probably a good start, likely the most useful monad-generic (or applicative-generic) function, that is simple but incredibly powerful and useful.

More generally, Monads essentially support function composition between monadic functions, so you can use it to write code that is agnostic to the monad it runs in. This can let you write e.g. prod. Code that is in IO or Async or Maybe, but for unit testing run it in Identity.

Also, it allows syntax sugar such as do notation that makes it clear to work with even when you know which monad you're working in.

moomin · 2 months ago
Your basic problem is that your programming language can’t express the concept cleanly. You need what’s called “Higher-Kinded Types”.

To give you a concrete example, in C#

Func<A, B>, List<A> -> List<B>

Func<A, B>, Task<A> -> Task<B>

Func<A, B>, Func<C, A> -> Func<C, B>

Can’t be expressed using a generalisation. But in Haskell, you can write

(Functor F) => Func<A,B>, F<A> -> F<B>

One of the biggest things that makes monads hard to understand is that the type systems of most languages can’t represent them. Annoying, that includes the “typeless” ones.

ryandv · 2 months ago
See for instance the MonadIO typeclass from Haskell [0]. Constraining against this typeclass allows one to write monadic code / do-notation that works with any monad, so long as that monad supports the execution of IO statements.

Now for instance, arbitrary effects (error handling, resource management, etc) can be composed on top of an IO monad (e.g. via monad transformers), and MonadIO code, that is written to only depend on the IO effects, can still be executed in these contexts with more effects layered on top.

[0] https://hackage.haskell.org/package/base-4.21.0.0/docs/Contr...

ww520 · 2 months ago
Here is an analogy. List is a container whose elements can be any type. There are general operations applying to a list, e.g. map, reduce, filter, find, etc. Any data type (int, float, or bool) of list elements can use these same operations regardless.

It’s similar for monad. If you can provide a unit constructor to turn an object value into a monad value and a “map” operation that unwraps a monad value, applies a function to it, and wraps the result, you have monadized the object type. Your objects can participate in any algorithm operates on monads.

The monad algorithms are the same. The only things different are the unit constructor and the mapping function.

ChadNauseam · 2 months ago
Lots of useful generic code. MapM is a version of `map` that works with any Monad, `sequence` works with any monad, and so on. These are used very frequently.

But the bigger benefit is when syntax sugar like `do` notation comes in. Because it works for any Monad, people can write their own Monads and take advantage of the syntax sugar. That leads to an explosion of creativity unavailable to languages who "lock down" their syntax sugar to just what the language designers intended. In other words, what requires a change to other languages can often be a library in Haskell.

StopDisinfo910 · 2 months ago
The truth is that it’s not a very useful abstraction in and of itself.

You can build some generic tooling on top of monads and applicatives and that tooling is useful and can give familiarity to new data structures but objectively that’s true mostly because monads are so common in Haskell. Thinking monads are common for this reason is reversing cause and consequence.

So why are monads so prevalent in Haskell, you will ask. Because there is sugar to make their use easy. And why is there sugar? Because I/O uses a monadic interface. That was Haskell new idea. You can easily keep track of side effects with the type system if you use a monadic interface and some sugar.

lmm · 2 months ago
> If all monad instances work differently what is the value of the Monad interface? What kind of usefull generic code can one write against the Monad interface.

Code that composes a bunch of operations, for whatever kind of composition those operations need (some people call Monad "programmable semicolons", because it's a lot like sequencing). E.g. traversals of datastructures, or some kind of "do this in a context" operation. Essentially any function you pass a "callback" to should probably be written in terms of monads so that it can accept callbacks that need different kinds of composition beyond just being called at different points in the control flow.

jerf · 2 months ago
As I so often do, I find it helpful to analogize Monad to Iterator for questions like these, because it's a typeclass/interface/etc. that people are more used to and does not have that aura of "if I feel like I understand it I must not understand it" attached to it that blocks so much learning.

You extremely often use iterators in a context where there's no way you could usefully slot in just "any" iterator and have some useful code. Suppose you have an iterator that iterates over the links that appear in an HTTP document, and write some code to fetch the HTTP resources so referenced. Well, obviously, "writing against the iterator interface" doesn't do you any good in that case. It's not like you can slot in an iterator that iterates over prime numbers to such code and get anything out of it.

What you can do with the Iterator interface is provide extremely generic tools that can be used against any Iterator, like, take the first x, skip every other one, reverse the iterator list (if finite and for a price), filter the results against a type-specific acceptance function, all kinds of things: https://docs.python.org/3/library/itertools.html These tools do not depend on the details of what the iterator is or how it works, only that it is one. In this case you might even use something as powerful as "give me an iterator and a function to run against the value that comes out of the iterator and I will run it in a parallel map and limit the number of workers and handle errors in this specific way", but all that code has no specific knowledge about URLs or fetching things from the web or anything like that. It just knows it has an iterator and a matching function for the value coming out.

Similarly, "writing to the Monad interface" gives you access to a wide variety of tools that work across all things that implement the monad interface: https://hackage.haskell.org/package/base-4.21.0.0/docs/Contr... What exactly they do depends on the underlying monad implementation. It happens that they turn out to be very useful in practice a lot of the time.

You can also create new compositions of the tools that only pay attention to the interfaces, like, "drop the first x values and then filter the rest" for an iterator, though often the libraries ship with the vast majority of what you need.

Written against the interface specifically you can only use exactly what is in the interface. But you also have the concrete types to work with, with whatever it is they do. Just as you can't really do much "real work" against just "something that provides a next value" when you have no idea what that next "value" is, but iterators are very useful with specific types, monads are the same way.

(You can then later work up to code that is allows swapping out which monad you may be using depending on how it is called, but I prefer to start here and work up to that.)

Dead Comment

PaulHoule · 2 months ago
I came to the conclusion that a List<X> is a good generic data structure, for instance in cases where the cardinality is supposed to be 0..1 it is often less trouble than a nullable scalar or an Optional<X> and you have cases where you’re going to get a list anyway such as if you are querying a relational database. (Often people write awkward code to turn that result into a nullable/Optional and then more awkward code to turn it back to a list later) Lists work almost exactly the same in most languages whereas there is often something weird about null, there might not be Optional, it might have something weird about it, etc.

For multi-language distributed processing, particular if JSON is involved it’s worth a try.

To be fair I write a lot of Java where Optional is a train wreck in so many ways not least it could be null anyway, you are allocating objects needlessly, and I just see people get hypnotized by awkward code also they write bugs or scan right past them.

instig007 · 2 months ago
yes, exactly, not sure why your comment was downvoted. Also, generally it's not cardinality of 0..1, it is `[]` vs `xs:[]`, that is - either empty, or a multitude of values, where 1 is a specific instance of the multitude (singleton).
lo_zamoyski · 2 months ago
I think "monad" is overloaded, or at least there are varying depths of understanding that are confused.

From a programming perspective, the definition of monads is clear.

  bind :: m a -> (a -> m b) -> m b
  return  :: a -> m a
You can start using monads immediately, and in a language like Haskell, things click fairly quickly, because monads are used everywhere and taken seriously in that language.

But the implications and consequences of this definition for monads aren't always obvious, like how they can be used to structure operations or whatever.

And then there's the category theoretic business of monads which you don't need to understand for most programming purposes. That might be a source of confusion. As you more or less say, people have vague, built up expectations about monads. They expect something heavy and mysterious and doubt they're understood them according to the first definition. But the basic definition is straightforward.

Numbers are like this, too. You understand what a number is (a quantity). You can perform all sorts of operations and calculations using them without knowing number theory or the philosophy of mathematics.

polygot · 2 months ago
Thanks for the feedback! I didn't expect my post to garner a lot of attention. I am totally ok with rewriting part 1, potentially to make it more concise to prevent confusion (wow, this post is super long, monads must be complex!) is what I'd like to avoid.

I can reproduce the double line issue in part 2, this was my mistake and I missed it as part of my editing process. I'll delete part 2 while I make the corrections.

Tainnor · 2 months ago
> In my experience, people new to Haskell focus way too much on getting the "a-ha" moment for monads in general, [...]

I feel this is true in general for mathematics (and therefore by languages whose design is heavily inspired by maths). A lot of people not familiar with university-level maths think that they need to understand what some mathematical concept "really means", but modern mathematics is a structural science. It looks at things that have entirely different semantics (symmetries, conservation laws, integers, matrices, Rubik's cubes, ...) and noticing that they all have the same structure (they're all groups) and therefore we can say something about all of them simultaneously.

That doesn't mean that intuition is useless. Once you have thoroughly understood what makes a group a group or a vector space a vector space, it's totally normal to e.g. consider a space of functions and think of them in your head as if they were arrows in a Euclidean space (the analogy breaks down at some point, but it can carry you a certain way). That's also why it's fine to think of a monad as a container or as a burrito or whatever once you've actually understood the concept. But you can't really short-circuit this process in my opinion.

kqr · 2 months ago
> one separate lesson for every common monad instance.

Right on. This is the "What Are Monads" fallacy: https://entropicthoughts.com/the-what-are-monads-fallacy

brooke2k · 2 months ago
Wow, this is a great post, thank you for sharing. It echoes my thoughts exactly.

Deleted Comment

deepsun · 2 months ago
Yes, and I don't even see the value in generalizing to one Monad concept. It only makes things worse, as then one is tempted to share terminology between different kinds of monads. E.g. there's no reason Maybe's flatMap is called the same as List's flatMap, it might be more readable to call them differently, as some libraries do.
Bjartr · 2 months ago
A container monad is just a recipe monad where the recipe for getting the value is "here's the value"
billmcneale · 2 months ago
> That's why the pattern is so useful in the first place

How useful, really? Monads don't even universally compose, which is what most people sell the concept for.

lambdas · 2 months ago
Actions compose, types (generally) don’t. So Monad X and Monad Y may not make a valid Monad Z, but Kleisi composition very much exists for actions within a monad.
brooke2k · 2 months ago
Monads don't compose between different instances, but monad transformers do.
monkeyelite · 2 months ago
Also formal mathematical objects aren’t always like real world objects. What’s a ring? It’s a thing with these properties
magarnicle · 2 months ago
I found helpful. It really clarified a few things for me.
bdangubic · 2 months ago
with this comment you joined a list of seven million devs that have written a monad tutorial :)
Avshalom · 2 months ago
I mean at it's most basic "monads" are

-a data type with some hidden information

-a lot of functions that can ignore that hidden information

-some functions that can act (switch|add|mutate|signal|sequence) on that hidden information

people seem to think talking about flatMap somehow makes this intuitive despite the tautological issue of flatMap only making sense if you already know what's going on.

kerblang · 2 months ago
The way I think of it, monads are a solution to Callback Hell, where you've fallen in love with lambdas, but now you have a nightmarish mess of lambdas in lambdas and lambdas calling lambdas. The monadic functions allow you to create "for comprehensions" aka "do comprehensions" but really, they look like a classic for-each loop. They secretly call the monadic map/flatMap/filter functions.

    for x in list
        doThings(x)
These comprehensions have a strange bonus feature, that you can do nested "loops" all at once, and even add "guards" (little if statements)

    newlist=
        for x in list1
            y in list2 if y > 3
            z in list3
            doThings(x, y, z)
But again, the comprehension, when "de-sugared", is secretly calling the map/flatMap/filter functions of list1, list2, list3 to get our result. You as the author of a given monad can implement those functions however you want, and they're all 3 lambda based. But notice how the comprehension is flattening those lambdas out! Our callbacks in callbacks are much more readable like this.

Without comprehensions, you can still implement monadic functions in any old language (probably in C...?), and they're handy in their own right, but you don't get the flattening-of-callback-hell magic.

stronglikedan · 2 months ago
After reading your comment, I've made it my mission to understand it. Although I have no idea what you're talking about, you make it sound intriguing.
kerblang · 2 months ago
First off, I'm not sure it's even worth it to understand this stuff... Second, someone should be along to slam it soon enough and insist I've missed some gibberishy business that you'll never understand.

With those caveats in mind, here's a more intensive scala-based monad tutorial I made:

https://github.com/zaboople/techknow/blob/master/scala/monad...

But really, don't burn up too much of your short life trying to come to terms with this stuff. There's a reason most languages don't get around to supporting Monads...

still_grokking · 2 months ago
What parent describes is pretty simple: That's just how the compiler transforms some code.

Do-notation in Haskell, or for-comprehensions in Scala are just syntax sugar for nested calls to `flatMap`, `filter`, and `map`.

I think this here shows it nicely:

https://www.baeldung.com/scala/for-comprehension#for-compreh...

In Scala you can add the needed methods to any type and than they will "magically" work in for-comprehensions. In Haskell you need to implement a Monad instance which than does the same trick.

The concrete implementations of these methods need to obey to some algebraic laws for the data structure which defines them to be called a monad. But that's pretty much it.

In my opinion all that Haskell in most "monad tutorials" just blurs an in principle very simple concept.

The in practice relevant part is that a monad can be seen as an interface for a wrapper type with a constructor that wraps some value (whether a flat value, some collection, or even functions, makes no difference), does not expose an accessor to this wrapped value, and has a `flatMap` method defined. It also inherits a `map` method, coming from an interface called "Functor". The thing is also an instance of an "Applicative", which is an interface coming with a `combine` method which takes another object of the same type as itself and returns a combination of again the same type (classical example: string concatenation can be a `combine` implementation if we'd say that `String` implements the `Applicative` interface).

lmm · 2 months ago
You might like https://philipnilsson.github.io/Badness10k/escaping-hell-wit... which is a longer version of the same kind of argument.
nine_k · 2 months ago
To get a minimal idea, you can think about a monad as of a parametrized class: M<T>. Its functioning follows "monad laws" that allow you to do certain things with it, and with the value(s) of T wrapped my it. In particular, you can always "map" the values:

  M<T1>::map(f: (T1 -> T2)): M<T2>
  List<int>([1, 2, 3]).map(x => toString(x)) == List<string>(["1", "2", "3"])
You can always flatten the nested structure:

  M<M<T>>::flatten(): M<T>  // [["a", "b"], ["c", "d"]] -> ["a", "b", "c", "d"]
This is usually expressed in a different form, more fundamental:

  M<T1>::flatMap(f: (T1 => M<T2>)): M<T2>
  List(["a b", "c d"]).flatMap(x => x.split()) == List(["a", "b", "c", "d"])
You can notice how that map() thing does looping over a sequence for you.

But Optional<T> is also a monad:

  let x: Optional<int> = Some(1);
  let y: Optional<int> = Nothing;
  x.map(n => n + 1).map(n => n * 2) == Some(4);
  y.map(n => n + 1).map(n => n * 2) == Nothing;
As you see, the same map() (and flatMap()) does the condition checking for you. and can be chained safely.

You can also notice how chaining of map-like operations does operation sequencing:

  fetch(url).then(content => content.json()).then(data => process(data))
Your language, like JS/TS, can add some syntax sugar over it, and allow you to write it as a sequence of statements:

  async () => {
    const response = await fetch(url);
    const data = await response.json();
    process(data);
  } 
Promises are not exactly monads though, a Promise<Promise<T>> immediately transforms into Promise<T>. But other monadic properties are still there.

anchpop · 2 months ago
I wrote a post about a highly related topic here. It may be helpful to you in understanding the parent comment: https://chadnauseam.com/coding/random/how-side-effects-work-...

Dead Comment

nemo1618 · 2 months ago
I think this adds more confusion than it removes.

A list is not a monad. A list is a data structure; a monad is more like a "trait" or "interface." So you can define a List type that "implements" the monad interface, but this is not an inherent property of lists themselves. That's the sense in which a list "is a" monad: the OOP sense.

Haskell's List monad provides a model for nondeterminism. But that certainly isn't the only way List could satisfy the monad interface! It was a deliberate choice -- a good choice, possibly the best choice, but a choice nonetheless.

instig007 · 2 months ago
> A list is not a monad. A list is a data structure; a monad is more like a "trait" or "interface."

You operate with things that are bound to PL notions of specific languages. Instead, consider that list isn't a data structure, it's an abstraction that defines a multitude of position-ordered values. The multitude of position-ordered values called "list" is a presented entity of "monad", because it can be used as a valid input for a monadic computation defined consistently (in terms of the monad laws).

polygot · 2 months ago
Hi, I completely agree. "A" list isn't inherently a monad, and that is where my metaphor starts to fall apart a bit (my post title furthers this issue.)

I can clarify this earlier in part 1 or 2 instead of in to-be-written part 3.

danieltanfh95 · 2 months ago
Its a harmful metaphor and clickbait title.
Jaxan · 2 months ago
Isn’t it the case that for a given functor (on Set) there can only be at most one Monad structure?
whateveracct · 2 months ago
Nope. It's that there's only one lawful Functor instance. But Applicatives and Monads can be multiple - lists are the classic example (zip vs cross-product)
blakehawkins · 2 months ago
Can you explain the nondeterminism part of your comment more?
wk_end · 2 months ago
When you bind with (the Haskell definition for) the List monad - `someList >>= \someElement -> ...` it's like you're saying "this is a forking point - run the rest of the computation for each of the possible values of someElement as taken from someList". And because Haskell is lazy, it's (pardon the informality here) not necessarily just going to pick the first option and then glom onto it if it, say, were to cause an infinite loop if the computation were eagerly evaluated; it'll give you all the possibilities, and as long as you're careful not to force ones that shouldn't be forced, you won't run into any problems. Nondeterminism!

A nice demonstration of this is writing a very simple regex matcher with the List monad. A naive implementation in Haskell with the List monad Just Works, because it's effectively a direct translation of Nondeterministic Finite Automata into code.

pkal · 2 months ago
From automata theory, you might know that nondeterministic automata are represented by a set of states. Deterministic automata are always in a specific state, while nondeterministic ones are in multiple at once. Lists are used for non-determinism in Haskell the same way as a set, mainly because they are easier to implement. But the total order that a list induces over a set is not that relevant.
ryandv · 2 months ago
Determinism, in that given some set of inputs you only ever receive one output.

Non-determinism, in that given some set of inputs it's possible to receive a collection (a list) of possible outputs.

With lists you can express things like all possible pairings of all possible outcomes, or the Cartesian product:

    ghci> liftM2 (,) ['a', 'b', 'c'] [1,2,3]
    [('a',1),('a',2),('a',3),('b',1),('b',2),('b',3),('c',1),('c',2),('c',3)]
... or in more explicit monadic do-notation:

    ghci> :{
    ghci| do
    ghci|   x <- ['a', 'b', 'c']
    ghci|   y <- [1,2,3]
    ghci|   return (x, y)
    ghci| :}
    [('a',1),('a',2),('a',3),('b',1),('b',2),('b',3),('c',1),('c',2),('c',3)]
and so on.

gr4vityWall · 2 months ago
I think the most intuitive description for a monad I've ever seen is 'flatMappable'.

Context: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Usually articles that describe them in a very Math-y way go above my head. But the definition above was immediately clear (I saw it on HN).

I think this article is a bit more approachable than others I've read, but it still gets very confusing near the end.

hirvi74 · 2 months ago
Reminds me on an analogy for a monad I once heard. Not sure if it is correct because I lack the mathematical understanding to verify.

Anyway, a nested To-Do list is (allegedly) a common form of a monad. Say I am trying to clean my whole house. Well, I could have an item for a task like cleaning the kitchen that has each task I need to do in the kitchen in order for the kitchen to be cleaned. I can do the same for the living room, garage, etc..

However, that is mainly for organization purposes. While I may write the tasks in a nested manner, I could very well write each item as just a long flat list of To-Do tasks, and in reality, all the tasks are effectively completed as if they were one large flat list.

Is that kind of what you mean by 'flatMappable'? Nested To-Do lists being flattened and mapped to one large list? As in, a To-Do list of To-Do lists is just a single, larger To-Do list?

skybrian · 2 months ago
Not exactly. The flatMap() operation itself isn’t going to flatten an arbitrarily nested todo list. It just concatenates the lists that are returned after applying the callback to each item.

The monad part is about what happens if you call flatMap() repeatedly. That is, each call to flatMap() is one action, and these actions can be nested without affecting the result.

gr4vityWall · 2 months ago
Yes, your understanding is correct. It's literally calling map() on an array, followed by a flat().

Sorry, I should have added more context to my post. I edited my post and added a link to the MDN definition of the flatMap function.

aadhavans · 2 months ago
Could you elaborate on that? What does 'flatMappable' mean in this context?
IshKebab · 2 months ago
This is a good explanation:

https://users.scala-lang.org/t/what-is-a-monad-in-scala/4169

It's like... what would you call all types that have a .write() method? Writable right? What would you call all types that have a .dispose() method? Disposable. What would you call all types that have a .flatMap() method? Monad obviously.

gr4vityWall · 2 months ago
kevinventullo · 2 months ago
So I come at this from a math background but I’ve always found these explanations to be overly complex. In the parlance of C++, I think of a monad as a template class T with the following properties:

1. For any class X, there is a canonical method

  F: X -> T<X>
2. For any class X, there is a canonical method

  G: T<T<X>> -> T<X>.
3. For classes X and Y, and any method

  f: X -> Y, 
there is a corresponding method

  “T<f>”: T<X> -> T<Y>.

—————-

Here “any type” means any type that is compatible with the template.

And then there’s some additional rules which make all these methods compatible, based on the different ways of stacking nested T’s and the “canonical” maps you get. Admittedly there is some confusing accounting here, but I also think most natural ways of constructing the above three requirements are going to satisfy them anyway. For List and Maybe it’s fairly obvious what the above methods are.

I dunno, maybe I have it wrong and someone can correct my understanding.

Kranar · 2 months ago
This is like explaining chess by simply stating the rules. Like sure explaining the rules of chess is important but only knowing the rules provides for nothing more than a superficial understanding of a topic.
monkeyelite · 2 months ago
Yes, this is how you learn math. It helps to pair definitions with examples and exercises.
kevinventullo · 2 months ago
I mean if someone is learning chess for the first time, then yes you should start with the rules rather than jumping right into waxing philosophic about positional strategy to show off how smart you are.
tel · 2 months ago
Yeah, that's correct. You also often see it as having that for any method `X -> T<Y>` there's a corresponding method `T<X> -> T<Y>`. Or you can have that for any two arrows `X -> T<Y>` and `Y -> T<Z>` there's a composed arrow `X -> T<Z>`. All are equivalent.
kevinventullo · 2 months ago
Thanks for confirming; I wasn’t 100% sure what I wrote was equivalent to the `X -> T<Y>` definition, but I could see how you’d get one from the other using the unit/counit.
gpderetta · 2 months ago
A monad would not be a template class in C++, it would a concept (more or less the c++ equivalent of an Haskell type class).
feelamee · 2 months ago
monad like an idea - yes, it will be a concept. But a concrete monad - like list or maybe - will be a template class. Probably comment author means this
4ad · 2 months ago
So you think that a monad which is an object with a simple definition in category theory is better explained in terms of C++?

I would agree that most of these articles about monads are bad. Just study the definition, then study what you can do with monads, it's not that hard.

kevinventullo · 2 months ago
For people who are more familiar with C++ than category theory, yes.
recursive · 2 months ago
Yes.

If you don't already know category theory, learning it is hard. The terms on wikipedia seem to form a dense graph of links. It's hard to get a foothold of comprehension. For people that already know C++, or are at least familiar with this syntax, this is more useful than describing it in haskell syntax or category theory. There seems to be a chicken and egg problem regarding haskell and monads. Learning c++ may be harder or easier than category theory. I'm no sure, as I don't understand either one of them. But this syntax makes more sense to me than something expressed in terms of category theory vocabulary.

t43562 · 2 months ago
Another tutorial which makes monads about 100x more impossible to understand for me by relating them to something else and describing all the weird ways that they are NOT that thing.

IMO if you already have it, this will be a lovely comparison full of insight, but if you haven't then it's full of confusing statements.

IMO what they are is utterly unimportant, except to mathematicians, and what you can do with them is more to the point.

The fact that explanations are so often in Haskell just makes them more unintelligible because you really need to know what problem they solve.

empath75 · 2 months ago
The reason that the explanations are all in Haskell is that Haskell is the only language that is reasonably popular that implements monad and calls it a monad, and 90% of the people looking up "What is a monad" are trying to learn Haskell.
t43562 · 2 months ago
Yes, you're doing 2 difficult things at the same time - a new language and a new concept. IMO it would be great to just have the concept to deal with.
polygot · 2 months ago
Thanks for the feedback! I'll likely be editing part 1 to include the feedback so far from the commenters as well. If there's a specific statement or analogy that felt especially confusing, please point it out and I'll clarify it in the post.
t43562 · 2 months ago
Sorry for moaning - it's just the usual despair that I feel every time I read a new explanation and fail to understand it. This isn't your fault.
robinhouston · 2 months ago
I expect the author has done this knowingly, but the title is rather painful for a mathematician to read.

A list is not a monad. List is a monad. A list is an algebra for the List monad.

Garlef · 2 months ago
What you said is not correct!

In detail:

* "A list is not a monad" - True!

* "List is a monad" - True. But I think "`List` is a monad" might be clearer.

* "A list is an algebra for the `List` monad." - False!

What's correct is the following:

* "An algebra for the `List` Monad is precisely a monoid."

Sketch of the construction:

(an algebra for the list monad is a monoid): Recall that an algebra is a set/type `A` together with a function `mult: List A -> A` together with some axioms. Think of such a function `mult: List A -> A` as the function that assigns to each list with elements in `A` the product over all those elements. The aforementioned axioms boil down to: (1) `mult([])` is a neutral element and (2) `mult` is an associative binary operation when restricted to two-element lists.

(a monoid is an algebra for the list monad): Same Idea - Given a monoid, we define a function `mult: List A -> A` by assigning to a list of elements of `A` the product of these elements. And the empty list we assign to the neutral element. Then we can use the associativity and properties of the neutral element to show that this function constitutes an algebra for the list monad.

robinhouston · 2 months ago
You're quite right! Thanks for the correction. I should have said that a list is an element of a free algebra over the List monad, which is less pithy.
polygot · 2 months ago
Thanks for the feedback! I can definitely rename the post soon as a first step, although this may require rewriting a chunk of the article to more accurately reflect the fact that List is a monad, and not "a" list.

I could make this distinction in part 3 (not written yet) although I want to balance not misleading readers, but not overcomplicating it too early on.

leoh · 2 months ago
I appreciate you mentioning this because I think it’s actually an important point
1-more · 2 months ago
IDK if it ads anything to the article, but `map` is a property of Functors, and every Monad is a Functor. Well, every Monad is an Applicative Functor, and every applicative functor is a functor.

All a way of saying that, yep, you always have `map` when you have a Monad, but you don't need a Monad to have `map`.

If you want an example we can compare a regular list and a Ziplist. A regular list's Applicative instance does a cross product, while a Ziplist's applicative instance does a dot product.

    (*) <$> [2,3] <*> [5,7, 11]
    --> [10,14,22,15,21,33] 

    getZipList $ fmap show $ (*) <$> ZipList [2,3] <*> ZipList [5,7, 11]
    --> ["10","21"]  
There's no great way to write a Monad instance for ZipList. But it's an Applicative Functor and thus is also a Functor and thus you can map over it. https://www.mail-archive.com/haskell-cafe@haskell.org/msg572...

For quirky reasons in Haskell, `fmap` the function implemented for every Functor instance. This is because `map` was already taken by lists. Weird, I know.