The epiphany I had with haskell was understanding why it was so modular. It goes beyond just understanding haskell though and towards understanding the fundamental nature of modularity.
Think about it. What is the smallest most primitive computation that is modular?
If you have a method that mutates things, then that method is tied to the thing it mutates and everything else that mutates it. You can't move a setter away from the entity it mutates without moving the entity with it. It's not modular.
What about an IO function? Well for an IO function to work it must be tied to the IO. Is it a SQL database? A chat client? If you want to move the IO function you have to move the IO it is intrinsically tied with.
So what is the fundamental unit of computation? The pure function. A function that takes an input and returns an output deterministically. You can move that thing anywhere and it is also the smallest computation possible in a computer.
The reason why haskell is so modular is it forces you to segregate away IO and purity. The type system automatically forces you to make the code using the most modular primitives possible. It's almost like how rust forces you to code in a way that prevents certain errors.
Your code in haskell will be made entirely of "pure" functions with things touching IO being segregated away via the IO monad. There is no greater form of modularity and decoupling.
In fact any other computing primitive you use in your program will inevitably be LESS modular then a pure function. That's why when you're programming with OOP or imperative or any other style of programming you'll always hit more points where modularity is broken then you would say haskell. That is not to say perfect modularity is the end goal, but if it was the path there is functional programming.
The way I like to picture the IO monad is you don't have direct access to the values coming from IO. You can only give the monad some functions which it applies to the values. Among these functions there are some that tell the monad to send values back out (print to screen, send request, etc.). Basically, all these functions make your program.
Think about it. What is the smallest most primitive computation that is modular?
If you have a method that mutates things, then that method is tied to the thing it mutates and everything else that mutates it. You can't move a setter away from the entity it mutates without moving the entity with it. It's not modular.
What about an IO function? Well for an IO function to work it must be tied to the IO. Is it a SQL database? A chat client? If you want to move the IO function you have to move the IO it is intrinsically tied with.
So what is the fundamental unit of computation? The pure function. A function that takes an input and returns an output deterministically. You can move that thing anywhere and it is also the smallest computation possible in a computer.
The reason why haskell is so modular is it forces you to segregate away IO and purity. The type system automatically forces you to make the code using the most modular primitives possible. It's almost like how rust forces you to code in a way that prevents certain errors.
Your code in haskell will be made entirely of "pure" functions with things touching IO being segregated away via the IO monad. There is no greater form of modularity and decoupling.
In fact any other computing primitive you use in your program will inevitably be LESS modular then a pure function. That's why when you're programming with OOP or imperative or any other style of programming you'll always hit more points where modularity is broken then you would say haskell. That is not to say perfect modularity is the end goal, but if it was the path there is functional programming.