For example I don’t understand (beyond the concept as a whole): 1. How do monads allow side effects if you can’t have side effects in Haskell? 2. Are monads just a name for a reccuring way to aproach a problem? 3. In what scenario would you use a monad, or for what type of problem?
"Functor", "Applicative", and "Monad" are all just generalizations of the concept of `map` and `flatMap`.
1. Something is a "Functor" if you know how to call `map` on it, nothing more. "I can take a box of things and turn it into a box of other things, 1-to-1". On lists, for example, this is just `map` itself
2. Something is an "Applicative" if you know how to call `map` on it, but also know how to take a non-boxed value and put it in a box, and also know how to combine boxes in order
3. Something is a "Monad" if you know how to do all of the above, but also know how to call `flatMap` on it, nothing more. "I can take a box of things, turn each thing into a new box, and then combine them all in order". On lists, for example this is just `concatMap`
There's nothing really more complex to it, besides how you squint at various things (like functions) to fit them into the concept of `map` and `flatMap`.To answer your questions more directly:
1. Monads themselves are neither necessary nor sufficient to perform side effects in Haskell; they don't directly enable the effects, but they *do* help place guardrails on the actual unsafe, low-level code which *can* perform the effects, safely and in an ergonomic and composable way
2. Yes, "Monad" is just a name for a recurring way to approach a problem. Like in most math and programming, a certain repeating pattern was noticed, and given a name. Because of the math origin of the term, you get "Monad" instead of "flat-mappable"
3. Like any other tool, you reach for a monad when you have a monad-shaped problem. They're just one (powerful) tool for solving certain problems [1, 2, 3] :: [Int]
Here, the "box" is a list, and inside of it are the values 1, 2, and 3.As you know, `map` is an operation that converts the values inside of the box into other values; for example, adding 1 to every element:
[1, 2, 3] :: [Int]
| | | (+ 1)
v v v
[2, 3, 4] :: [Int]
But the operation you perform with `map` doesn't need to keep the values of the same type: [ 1, 2, 3 ] :: [Int]
| | | (show)
v v v
["1", "2", "3"] :: [String]
The operation can also produce new boxes! Since `String` is actually itself a list (`[Char]`), the result above is the same as [ 1, 2, 3 ] :: [Int]
| | | (show)
v v v
[['1'], ['2'], ['3']] :: [[Char]]
In some cases, you might want to "flatten" this box-of-boxes together. In some languages this operation is called "flatten"; for lists in Haskell, it's called `concat` [['1'], ['2'], ['3']] :: [[Char]]
| | | (concat)
v v v
[ '1', '2', '3' ] :: [Char]
This example isn't terribly motivating, but you can see when you have deeper lists-of-lists how this might be handy: [[1,2,3], [4,5,6], [7,8,9]] :: [[Int]]
| | | (concat)
v v v
[1, 2, 3, 4, 5, 6, 7, 8, 9] :: [Int]
Here, we took a collection of boxes (`[[Int]]`) and combined them in order (sequentially) to produce a new box (`[Int]`).What other languages call `flatMap` is just a `map` operation followed by a `flatten` operation. Very roughly, `Functor` gives you "map" (`map`), `Applicative` gives you "flatten" (`concat`), and `Monad` gives you "flatMap" (`concatMap`).
The power of these comes from considering different types of "boxes". `Maybe`, for example, works almost like a list that can contain up to 1 element, and its operations behave pretty much identically. Other types are interesting because how you define their "box-ness" can lead to interesting/useful results. It can be tough to envision how, e.g., a function could look like a "box", but it turns out that you can define rules for it that make it useful. (What does "map" look like for a function? Well, it turns out that mapping a function over another function is already just... function composition!)
You can go a lot deeper into these definitions, and it helps to look at some implementations to grok them better, but the core concepts themselves are not very complicated. The "magic" is in how you define the "boxes".
A monad is kind of like a generic class to boxes that adds additional logic to the data it boxes without actually caring about the data itself.
My goto monad for that concept is a linked list and the map operator. So an instance of the linked list might be Node(5) -> Node(7) -> EmptyList. Now let's call map with a function f(x) = str(x) + " * 2 = " + str(2x). This gives us Node("5 2 = 10") -> Node("7 * 2 = 14") -> EmptyList.
Now let's separate the monad from this. The monad is the structure and logic around the data and the function that we provide. The monad doesn't care what data it holds and is doesn't care what function we provide. It only defines the structure and how functions are applied to the data it holds.
This is how "side effect" information is carried outside the actual information the monad is carrying. Real side effects like writing to disk however are not real monads. The IO monad pretends to be a real monad but it's not. It just helps with hiding the impurity of IO from the pure parts of Haskell.
So one might think about the IO monad kind of like the list monad where the order of the IO operations is encapsulated. It kind of pretends that the IO part is pure and deterministic when its actually not in order to have a somewhat clean separation between pure and non pure parts of the program.
Hope that helps.
1. Is answered in the parent comment
2. It's not really about solving problems directly. Its more about a common way to hold meta information about your data. But of course that might help you with approaching similar problems.
3. Thats hard. You dont really think about it that way. Again its not really the type of problems directly. Its more about similar approaches to solving different kinds of problems. So you can use monads for solving basically any problem. But you can also choose to not use monads to solve the problem. This is something you'll need to get a feel for. And it shouldn't be for first line of thought. When solving a problem you might recognize that a certain monad lends itself to solving the problem. But it's not really something you actively go looking for.