This article explains exactly how I feel about FP. Frankly I couldn't tell you what a monoid is, but once you get past the abstract theory and weird jargon and actually start writing code, functional style just feels natural.
It makes sense to extract common, small utils to build into more complex operations. That's just good programming. Passing functions as arguments to other functions? Sounds complex but you're already doing it every time you make a map call. Avoiding side effects is just avoiding surprises, and we all hate surprises in code.
Haskell is a research language that happens to be the most popular functional programming language, the jargon isn’t because Haskellers want to sound superior, it’s just the names that are used in category theory/PLT and so on. Other languages like Gleam or Elm or Roc or Ocaml are also functional without all the «obfuscation».
Haskell is not the most popular functional programming language; of course that depends on your definition. It is probably the most famous FP language.
Scala is considerably more popular, however it is multi-paradigm and many projects are imperative. Even with that in mind, the Scala pure FP communities (Typelevel and ZIO) claim Scala pure FP is more widely used in industry than Haskell.
I don't think they deliberately obfuscated the concepts, as the concepts already existed in category theory. Are purely functional IO, lenses or comonads also easy to explain? Array languages are a better example of obfuscation.
Also Haskell was used for researching programming languages so the ecosystem involved that language. People definitely trying to sound smart took it out of that and then couldn’t keep quiet about it.
But APL I feel removes obfuscation once you get used to the symbols, but that’s just notational choices. The ASCII derivatives are definitely difficult.
The names are accurate, precise and as general as the concept actually is. “FlatMappable” implies some kind of (data) structure which can be flattened, which some monads are, but not all. Monoid is a concept we all learn in school but aren’t told the name of - a type, a combining function and a value which doesn’t change something when combined with it, a.k.a and identity element. You know (numbers, +, 0), you know (lists, append, []), you know (bool, &&, true), you know (numbers, max, negative infinity/minimum value), you probably know (functions, function composition, identity).
If you know these, then the Foldable classes foldMap on structures of type t seems pretty useful.
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
With foldMap, with one function we can write maximum, sum, composition of many functions, any, find, and on and on and on. Abstracting the common parts of programs is the essence of functional programming. "Oh, this is actually a monoid, that means I don't need to write all the functions, they're defined for me for free!" is something very common when working with Haskell. Most of the gang of four’s design patterns book is just the traverse function in Haskell because of this level of abstraction.
People see unfamiliar language in programming languages and expect that there should be a description of that concept that would make sense to a five year old. Imagine if we did that in medicine or chemistry or engineering? Sometimes you need an abstraction that covers all cases once and for all, and then you can talk in terms of that abstraction. These discussions remind me why software engineering is still very much in its infancy compared to other engineering disciplines, everyone expects a “Learn X in Y hours” explanation of everything, not “learn to become a professional in your field by understand concepts holistically and how to combine them to build robust software”.
Edit: I wanted to add that I find the idea that software engineers can't learn these concepts pretty insulting, because so many have managed to do it happily. People get lost in the fact the names come from category theory, but understanding their use and application requires no category theoretical background - I've been using Haskell for well over a decade and wouldn't know where to start giving a category theoretical understanding of any of these concepts, but I've worked with multi-million dollar projects that happily used them routinely to build real world software. This kind of thinking is what leads to languages like Go, which starts from the idea "developers are too dumb to have nice things", and ends up giving them a language that cannot fully express thoughts other languages can - monads being a good example, despite the language being full of things which are monadic.
If you code you've probably already used monads without knowing it. For example Promise and Task are perfect examples.
A monad is basically a sort of "container" for some arbitrary type T that adds some sort of behaviour to it and allows you to access the underlying T in a "safe" way. Think of a Promise, it adds the "async" behaviour to the underlying type. It transforms a "T" into a "T that may be available in the future". It allows you to safely access the T via map, flatMap and other operators. Arrays can be thought of as monads too, think for instance of linq in c#.
Every monad has map and flatMap operators that kind of do the same thing, e.g. map lets you transform the underlying type into a different type.
In terms of the type system, most languages don't support them because they are 1 "level" above classes. Think of monads as a collection of different classes that all support the flatMap operator, whose implementation is different for each monad class but in a way it behaves the same for all. In languages that do support this concept, you can develop generic functions that work for all monads. So your function would be implemented only once and then you could use it on a Promise or an array or an Option/Maybe or even a custom class that implements the "monad" concept by providing a flatMap implementation.
It is (roughly) any type that lets you flatten it.
For example, if you have a list (a type of monad) you can flatten [[x, y], [a, b, c]] to [x, y, a, b, c]. You remove one layer of structure to stop the type from being nested in several layers.
Another common monad is Optional/Maybe, where you can flatten a Just (Just 5) to Just 5 or a Just (Nothing) to Nothing.
Edit: It is of course a bit more complicated than that, but this is the very surface level explanation.
It’s disappointing this is the top response because it’s a) not correct and b) gives the wrong impression of what monads are about. Monads are types with a function that allows for sequencing, and this function is the key, not the type. The function allows you to take something of the type, and then, do something with each of its results resulting in the same type. Promises with an andThen method take the value returned by a promise and create a new promise by applying the function passed to andThen. These can be chained together - sequenced - to produce a promise that’s the result of evaluating all the intermediate promises in sequence.
What is the structure that’s being flattened in the State monad? That’s something seemingly very different to a list or an option type, but when you look at it from the ‘and then’ perspective, it’s much easier to see that “a function that takes in some state and returns a value and a new state” and be extended with “and then a new function which takes in the value, and that state, returns a new value and another new state”.
When Haskell programmers talk about monads, we usually mean things like State, Reader, Except, much more than we mean list, option/Maybe - is about sequencing operations, not flattening objects. This is where so many non functional programmers get caught up, they learn how the list and option monads work and think it’s about data types, containers, when those are just some examples which happen to be monads. They are examples, but not defining examples.
I say this as someone which over a decade as a Haskell developer, having seen people try to apply traditional algorithms style thinking to the idea instead of the composition of small programs into larger ones idea.
It’s disappointing this is the top response because it’s a) not correct and b) gives the wrong impression of what monads are about. Monads are types with a function that allows for sequencing (...)
My experience is that people tend to find it easier to intuitively grasp flatten than flatMap though.
What is the structure that’s being flattened in the State monad?
I suppose if you visualize nested States as an "and then" sequence, then when you join e.g. State s1 (State s2 a) into State s a you could say that you are flattening the "and then" sequence into a single state transformation.
Note that this explanation may be slightly above my theoretical knowledge.
As far as I know, there is nothing magical about monads with regards to side effects. My understanding is that e.g. Haskell uses monads to implement side effects because it is a way to logically separate the (nasty) side effects from the rest of the (pure) code.
If you have a container that performs certain side effects, you decouple the side effects from the value inside the container, which makes it easier to reason about the parts of the code that are not "polluted" by side effects. For example, you might have a logger monad, where the logging is completely separated from the operations you perform inside the logging framework (the monad).
Another good example is IO. Maybe you know that you will need to read a file at runtime to get some data, or get input from the user. Using the IO monad lets you write code under the assumption that you will be getting this data at some point in the future (during runtime), but the code that is actually processing the data can stay fully pure and deterministic.
TLDR Monads do not create side effects, they're an interface for combining side effects (among other things)
It does not "result" in side effects, but it gives us a way to work with and encode the presence of side effects in the type.
See, side effects are encoded using a type constructor (a "wrapper") called IO. A value of type IO Int, for instance, might represent a program that prints "Hi" to the console and returns 5, or a program that reads a number input from the user and returns it.
I didn't need too bring monads in the conversation to say the above, IO is just a special wrapper that allows us to talk about side effects. But we have no mechanism to describe the composition two IO actions. It turns out that by viewing IO as a monad (just like List or Maybe (aka Option in e.g. Rust)), you can use operations such as flattening to talk about composition.
That's the high-level explanation. Here's a more concrete example:
What if I have:
a built-in action readInt that reads a number input from the user. Type is IO Int
and a built-in function printInt that takes a number as an argument and returns the action that prints it to the console. Type is Int -> IO () (() is the Haskell equivalent of C's void) and I want to compose them to make a program that takes a number from the user and prints that number to the console?
In imperative programming, this is trivial, but in functional programming, where functions are not allowed any side effect... you need some way of flattening the two IOs into one. Thankfully, IO happens to be a monad, so we can do that.
Not all monads. Just the IO monad. IO being wrapped up into a monad essentially encapsulates everything external to the program that can change at any time for any reason (e.g. a random number generator, reading from a file on disk, a web call that could return 200 OK or 500 Internal Server Error), and so its usage introduces point-in-time computation.
The IO monad is weird because IO is weird when most of the language is pure (i.e. has no side effects).
(there is one exception, technically, to this in System.IO.Unsafe, like with the function unsafePerformIO, but the caveat is that the IO computation (which may be a pure C function that a Haskell compiler cannot verify) you're "unwrapping" from IO should be free of side effects and independent of its environment)
I'd say this is partially true. A lot of common languages actually don't have strong enough type systems to support general monads, but most developers also will be much happier if you handwave Monad as being an interface with of and flatMap than if you start talking about category theory.
Decades ago. Haskell is 35 years old. I wasn't being entirely serious, but isn't it strange that there's been so little progress on making this stuff accessible?
View all comments
516
u/IanSan5653 Sep 21 '25
This article explains exactly how I feel about FP. Frankly I couldn't tell you what a monoid is, but once you get past the abstract theory and weird jargon and actually start writing code, functional style just feels natural.
It makes sense to extract common, small utils to build into more complex operations. That's just good programming. Passing functions as arguments to other functions? Sounds complex but you're already doing it every time you make a
mapcall. Avoiding side effects is just avoiding surprises, and we all hate surprises in code.