The simplest explanation is probably that Haskell is not that much better than, say, Java, for many of the software systems people write today. Why might this be?
The reason I’ll give is that Haskell’s otherwise excellent composability is destroyed at I/O boundaries, just like every other language. That is, we are at stage 4 above, where the bottleneck to further composition is these program boundaries. Since most software systems (especially those that span multiple nodes), have a large surface area in contact with the outside world, the code devoted to merely getting information at these boundaries into some more computable form is often the bulk of the work; once the data is in computable form, the actual computation needing to be done is easy.
Service boundaries is another perspective that I have never thought of but it makes complete sense. When I write webapps in java and spring, below is the list of setup that I need to do.
- ORM tools for CRUD operations
- Loggers for debugging purposes either syslog or system on-disk logs or external service like loggly, logentries
- Http API endpoints to provide json/xml/view response
- Controller receives the request, forwards it to service layer which performs external calls(db, third party) and returns the response to client.
In such a setup, there is no significant gain in using Haskell. Now compilers, interpreters are different beasts because the service boundary of these tools is very low i.e.) they are black boxes which don’t need to connect to external services and Haskell/OCaml shine here because of the deterministic characters of these languages.