Clearly they're not ad-hoc, since they can be used for all of those examples and more, whereas each of the ad-hoc solutions has to be implemented separately as a language-level feature.
> A generalized solution (shoving monads everywhere) may as well be much worse than a specific solution to a specific problem.
I think this is true in the small. Of course a dedicated syntax like async/await is going to be better suited to that specific problem than reusing a general-purpose solution like do notation. But once you're working on a larger codebase, being able to support new effects in a plain old library rather than having to have them built into the language by the language maintainer (or, worse, hacked-in by bytecode manipulation configured through annotations or something), and being able to generalise over multiple different effects or different representations of the same effect, is much more important.
(I do hold out hope that it's possible to get the best of both worlds by having dedicated syntax but only as lightweight sugar over the general-purpose syntax)
I won't hold my breath waiting for large projects coming out of Haskell.
If anything, boring and ad-hoc seem to win in the "large codebase" space. It's enough to look at all the projects that have come out (and keep coming out) of Java, and at the projects that are now coming out of Go and Rust. And then at the barren wasteland that is Haskell (or other languages so proud of their monads).
> they can be used for all of those examples and more
There's a probably a reason hardly anyone uses them as a language-level feature.
Well, boring languages will always win if you're counting lines, because they use a lot more lines to do the same thing. I work on a large Scala project and have worked on others before; these are real projects that produce a lot of business value, and would have been a lot more difficult or even impossible without those language features.
Nope. Never did I say or imply "number of lines".
But let's talk about the number of lines then. It looks like "boring languages" are quite happy without monads even though "once you're working on a larger codebase, being able to support new effects... and being able to generalise... is much more important." Seems to me, this statement must be supported by data.
Meanwhile let's talk about large projects. You know, the Hadoops, and the Cassandras, and the Kafkas, and the ActiveMQs, and the Tinkerpops, and the Mesoses, and the Nettys, and the Kuberneteses, and the Dockers, and the <insert dozens of other names here> of this world.
They are all developed and have taken over the world without monads at language level. Either they are small projects (they aren't) or the need to shove monads everywhere is highly exaggerated. Because I've yet to see an abundance of projects of the same calibre from the Haskell (and friends) world.
Kafka is written in Scala. That aside, I'm not convinced those are such large projects. How much logic do they need? People build large projects on top of them - I've done so - but where's the complexity in the components themselves?
What is a large project then?
Apparently, a distributed database isn't it. A distributed data processing framework isn't it. A high-performance web server isn't it. Nothing in the list is it.
What is "a large project"? Especially one, where monads are so important?
So far I only got "I think", "I work on a project", "I'm not convinced" and "I built a project on top of these". Hardly an argument, is it?
You do not need monads everywhere. Once you have a hammer, every problem looks like a nail. Monads are as ad hoc as any other solution listed there: that is, not ad hoc at all. These are all choices made by designers of the language. A generalized solution (shoving monads everywhere) may as well be much worse than a specific solution to a specific problem.
BTW. List comprehensions are amazing as long as they are not written with Pythonic syntax, but that's a minor complaint.