back

by zug_zug·1y ago·view on hn ↗
A monad is a weird/unnecessary concept for a type that wraps another type (and makes certain guarantees). Like you can have a List<Int> so List is a monad type. There's also an "option" type in many languages (which is just a list of size 0 or 1), which is a monad.

Some people call futures monads.

But the real takeaway is that people pretend they want you to understand all this type stuff, but they don't, they want to make up new complicated words for obvious ideas that you already understand, so they can feel smart. Once you understand it they aren't "smarter" anymore, so they make sure to explain every weird term they made up with 5 other weird terms they made up and insisting that all this abstract theory is relevant to everybody when it's not.

9 comments
The "(and makes certain guarantees)" is the important-and-not-unnecessary part, rather than a parenthetical.
A “type that wraps another type” describes some monads like List or Maybe but not other monads like the IO monad.

A monad is a type which support a particular pattern of function chaining. But the caining will have semantics which depend on the particular monad type, so it is difficult coming up with a metaphor which apply to all monads.

The IO monad, just like the state monad is still just a type wrapping another thing... In those cases, it is wrapping side-effects.

But I don't agree that a burrito effectively represents a monad purely because as you tried to allude to, a monad is not just about the fact that it is a wrapped time but about what functions are available to it.

Imagine a list with no map, is it really a monad?

https://swlaschin.gitbooks.io/fsharpforfunandprofit/content/...

That whole series is a great breakdown of functors and monads that really opened my eyes. This is near the end but that also means all the links are available.

> The IO monad, just like the state monad is still just a type wrapping another thing... In those cases, it is wrapping side-effects.

Side effects are not a type though. The “type wrapping another type” in the parent refers to List T and Maybe T where T is the wrapped type. But with IO you can have IO String or IO () representing operations with side-effects, so the analogy breaks down.

A common problem with half-baked monad explanations is they generalize from one or two monads and then create an analogy which breaks down for other common monads.

A future forms a monad because if you have a future that returns a future that returns an X, then there's a standard transformation from that to a future that returns an X
A list is a monad, but not because it wraps another type. What you're describing is higher kinded types.

A monad is a specific variant of a higher kinded type whose primary property lies in the fact that its value can be "evolved" via a specific function application (usually known as the "bind" operation). This sounds confusing but it's really a simple concept: if you take a higher kinded type, you can think about the values within it as the data and the type itself as metadata or a context of some sort. For example, an int is a piece of data, but a `Maybe int` is the same data with the added context of it being possibly absent.

A monad is not the only higher kinded type. Probably the most familiar HKT to working programmers (even if they don't know it) is the functor, which is a type that can be mapped over (in the sense of map-filter-reduce). If you have a `Foo<A>`, you can apply a function to turn it into a `Foo<B>`. However, a limitation of the functor is that you can only affect the data at the individual item level, not the context as a whole, so if you put a list of five values in, you'll get a list of five values back.

If we want to affect the context as well, we instead need a type like the monad, where we can "bind" a function that takes the inner value and returns a whole new wrapper based on it.

Why is this useful? Well, it can express a lot of different things very neatly - for example, fallible computation. Let's say you have two functions that both return a Maybe monad. In a language with nulls, you'd likely have to do something like `a = foo(); if (a is null) return; b = bar(); if (b is null) return`, which is tedious and error prone. Monads lend themselves to composing such chains extremely well, so you can simply do `foo().and_then(_ => bar())` and in the end have a value that combines the result (success or failure) of both of those functions.

Null values = chains of null checks = monads! Futures = chains of callbacks = monads! Mutable state = chains of writes = monads! Sequential execution = chains of statements = monads! And so on and so forth. You can get pretty crazy with it, not that I would recommend it.

Monads seem complicated and scary because most mainstream programming languages don't have the necessary abstractions to talk about higher kinded types as its own thing, but in reality most programmers are using monads daily without even realizing.

It reads as a little ambiguous to me in your comment, so just to be clear:

A polymorphic type - "a type wrapping another type" - isn't the same thing as a higher-kinded types.

Higher-kinded types are what let you express, in the language, the very notion of "a type wrapping another type". A "List Int" is a type wrapping another type; a "Maybe Int" is another type wrapping another type; but we can also say that "List Int" and "Maybe Int" (and etc.) can be abstracted over as "Monad Int"s. Monad is a higher-kinded type because it's a type wrapping types wrapping another type.

You missed the point though with that. It's explicitly not a monad if it doesn't have an equivalent of bind. If I need to write an imperative function to process the monad into another it's not a monad.

If however I have a List<byte> and would like it to become Maybe<UInt128>. A loop is always a jmp of some sort, a bind though could become a SIMD operation or be passed off to a coprocessor and I as the programmer would be none the wiser since all I cared about was the end result.

I don't regret learning how monads work, on the type-theoretic level. With that, it becomes very clear what things like null / None and operations like foo?.bar actually try to represent, or what transformations you can expect to work on various collections (like map() and flatmap() or filter()), how futures and promises work and how to easily combine them (and why JS promises are almost but not exactly monads, and where it matters), and, for bonus points, what does the "semicolon operator", that is, statement sequence, does from the logical point of view.

Understanding how state and IO can be represented as monads, and thus what useState() and useEffect() do in React, was also a nice consequence.

But certainly you are free to dismiss this piece of theory, and think about all these things as separate and peculiar. It usually very well suffices

>Some people call futures monads.

Well that's because they are. And it is one of my favorite examples to explain the power of monads actually.

Most humans prefer programming in the async/await style compared to using promises/futures with callbacks (commonly called "callback hell"). In most languages, you need explicit language support to enable async/await by adding new keywords into the language. (And you are completely at the mercy of your language designers. Looking at you Java.)

Haskellers, because they grok monads and futures are just monads, are able to code in an async/await style without any special syntax specifically for async/await. They just use the same notation they use for other monads.

> for a type that wraps another type

I think you are very confidently wrong here. Monads are not about wrapping types, as evidenced by the IO monad. You cannot say that a type is "wrapped" by the input-output system of a computer any more than you can say that a keyboard contains all possible character strings.

I am sorry to say but your comment is a cliche that's existed for at least the 15 years I've been using Haskell, where someone who doesn't understand a concept shows up, decries it as being a hoax and just a bogus complication on top of something simple, and while doing that displays not understanding it in the slightest.

But this existed before Haskell too, people have been doing what you're doing on the topic of structural programming, memory safe languages, regular expressions, dynamic html, ... html in the first place, microkernels, linux, unix, personal computers, minicomputers, computers, typewriters, looms, and electricity.

What would you call something that's all those things and can handle them in a uniform way with the ability to compose and transform the structure before applying the processing to data?
> A monad is a weird/unnecessary concept.

No it isn't.

A monad is a simple and easy concept that make it possible to write generic code that works across a number of very common things in software. The Monad interface is simple, and of immense practical utility.

There are only two things that are difficult about the concept:

1. The interface is so simple that it's hard to understand the purpose of it when you are looking at the interface disconnected from the concrete concepts that it abstracts over.

2. The interface was based on category theory concepts and created by people that have strong intuitions about category theory, so there is a lot of text floating around that presumes that background.

The essential thing here is that you don't need to know any of this in order to make use of the Monad interface or the generic code written against that interface.

> they want to make up new complicated words for obvious ideas that you already understand, so they can feel smart

> insisting that all this abstract theory is relevant to everybody when it's not.

Where is all of this bitterness coming from, my guy?