3 ms·
It's separation of concerns, yes, but that's only one half of it. The other half is that the relationship between those concerns is defined in a way that can be
by GeneralMayhem 11y ago
It's separation of concerns, yes, but that's only one half of it. The other half is that the relationship between those concerns is defined in a way that can be abstracted to apply to a whole host of other context-like properties (state, nullability, atomicity, lazy thunk execution, etc) and allow them to be manipulated uniformly. The monad laws allow operations over the monadic value to be handled exactly the same as pure values in cases where you don't care about the context, which is an extremely powerful abstraction.
Separating I/O concerns from the rest of your program can obviously be done in a number of ways. In Java or C++, I'd expect it to be done by doing all I/O in one class - either in main() or, in a bigger program, in a *OutputAdapter. If you keep all your impurity in the main loop and call exclusively pure functions from there, then yes, you end up with something that looks like the simplest case of Haskell's IO. But that programming style doesn't translate well to a functional model. It also doesn't provide, as Haskell does, a language-level guarantee that those functions you're calling actually are pure.
A monad doesn't have to represent impurity, and having impurity doesn't mean that you have to have a monad. You could just write your functions to carry around tuples where the first element is the actual value and the second is the context. But that doesn't scale when you want to be able to handle an arbitrary number of types of state in the same way, and it just so happens that the ways in which you generally want to compose stateful operations correspond to the mathematical construct of a monad.